docs: align config and github workflow (#314)

* docs: align config and positioning copy

* docs: remove internal branch workflow residue

* docs: record github sync guard

* chore: retrigger commit lint

* docs: leave readme files unchanged

* docs: remove public positioning comparison
This commit is contained in:
Elliot Chen 2026-06-29 07:31:31 +08:00 committed by GitHub
parent b7d15f7252
commit 0341f1230f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 389 additions and 66 deletions

View File

@ -26,8 +26,8 @@ standard. The format is enforced by `gitlint` in the `commit-msg` hook.
```
5. Never use `--no-verify`. If pre-commit hooks fail, fix the cause and re-commit.
6. Do not commit secrets, generated artifacts, or work-in-progress to a
protected branch (`main` / `dev` / `master`).
6. Do not commit secrets, generated artifacts, or work-in-progress directly to
the protected `main` branch.
## Types

View File

@ -1,43 +1,44 @@
---
name: new-branch
description: Create a branch following the project's GitFlow Lite model
description: Create a GitHub branch from main with the project naming convention
---
# /new-branch
Cut a new branch under the GitFlow Lite model.
Cut a new branch for the GitHub repository.
## Branch model
```
master = released / stable (tagged on release; protected)
dev = integration branch (protected)
feat/* = cut from dev → PR → merge into dev
fix/* = cut from dev → PR → merge into dev
hotfix/* = cut from master → merge into master AND synced into dev (double merge)
release = dev → master + tag on master (no separate release branch)
main = default and protected branch
feat/* = feature work
fix/* = bug fixes
docs/* = documentation-only changes
ci/* = CI, build, and developer-experience changes
chore/* = repository maintenance
refactor/* = behavior-preserving code structure changes
```
## Steps
1. Ask (or infer) the change type: `feat`, `fix`, or `hotfix`.
2. Pick the parent:
- `feat/*`, `fix/*` → branch from **`dev`**.
- `hotfix/*` → branch from **`master`**.
3. Update the parent first:
1. Ask (or infer) the change type: `feat`, `fix`, `docs`, `ci`, `chore`, or
`refactor`.
2. Update `main` first:
```bash
git checkout <parent>
git checkout main
git pull --ff-only
```
4. Create the branch with a kebab-case slug:
3. Create the branch with a kebab-case slug:
```bash
git checkout -b feat/<short-slug>
git checkout -b <type>/<short-slug>
```
5. For a `hotfix`, remember it must later merge into **both** `master` and `dev`.
4. Keep the branch scoped to one purpose and open a pull request back to
`main`.
## Naming
- `feat/add-agentic-rerank`, `fix/empty-profile-crash`, `hotfix/lancedb-conn-leak`.
- `feat/add-agentic-rerank`, `fix/empty-profile-crash`,
`docs/quickstart-config`, `ci/check-github-docs`.
- Lowercase, hyphen-separated, no spaces, concise.
Never commit directly to `master` or `dev` — always via a branch + PR.
Never commit directly to `main` — always use a branch and pull request.

View File

@ -10,8 +10,9 @@ Open a pull request on GitHub using the `gh` CLI and the repo's PR template.
## Steps
1. Confirm the branch and target:
- `feat/*`, `fix/*` → base **`dev`**.
- `hotfix/*` → base **`master`** (then a follow-up PR/sync into `dev`).
- Base branch is **`main`**.
- Head branch should be a scoped branch such as `feat/*`, `fix/*`,
`docs/*`, `ci/*`, `chore/*`, or `refactor/*`.
2. Ensure local checks pass first:
```bash
make ci
@ -24,7 +25,7 @@ Open a pull request on GitHub using the `gh` CLI and the repo's PR template.
4. Create the PR, filling the template
([.github/PULL_REQUEST_TEMPLATE.md](../../../.github/PULL_REQUEST_TEMPLATE.md)):
```bash
gh pr create --base dev --fill-first
gh pr create --base main --fill-first
```
Then edit the body to complete each section:
- **Summary** — what changed and why.
@ -38,4 +39,5 @@ Open a pull request on GitHub using the `gh` CLI and the repo's PR template.
- Keep the PR scoped to one area. Split unrelated changes.
- If `make ci` was not fully run, say so in Verification rather than implying it passed.
- A `hotfix` is not done until it has landed on **both** `master` and `dev`.
- Never retarget a community PR away from `main` unless a maintainer explicitly
asks for it.

View File

@ -7,7 +7,11 @@ on:
- ".github/ISSUE_TEMPLATE/**"
- ".github/PULL_REQUEST_TEMPLATE.md"
- ".github/workflows/docs.yml"
- ".claude/skills/**/*.md"
- "CLAUDE.md"
- "CONTRIBUTING.md"
- "scripts/check_docs.py"
- "scripts/check_github_contributor_docs.py"
push:
branches: [main]
paths:
@ -15,7 +19,11 @@ on:
- ".github/ISSUE_TEMPLATE/**"
- ".github/PULL_REQUEST_TEMPLATE.md"
- ".github/workflows/docs.yml"
- ".claude/skills/**/*.md"
- "CLAUDE.md"
- "CONTRIBUTING.md"
- "scripts/check_docs.py"
- "scripts/check_github_contributor_docs.py"
permissions:
contents: read

View File

@ -32,9 +32,9 @@ BibTeX citation information will be added here once the paper is published.
To cite the software itself:
```
EverOS: md-first memory extraction framework for AI agents
Version: 0.1.0
URL: https://github.com/EverMind-AI/everos
EverOS: local-first memory runtime for AI agents
Version: 1.1.0
URL: https://github.com/EverMind-AI/EverOS
License: Apache 2.0
```

View File

@ -47,9 +47,19 @@ Contributor engineering reference — build, test, CI gates, branch & commit con
## Branch strategy
`master` = released stable (hidden); `dev` = integration; `feat/* fix/*` → dev; `hotfix/*` → master + dev (sync).
`main` is the default and protected branch. Create scoped branches from
`main` (`feat/*`, `fix/*`, `docs/*`, `ci/*`, `chore/*`, `refactor/*`) and open
pull requests back to `main` after the required checks pass.
See [.claude/skills/new-branch/SKILL.md](.claude/skills/new-branch/SKILL.md) for the full GitFlow Lite rationale.
See [.claude/skills/new-branch/SKILL.md](.claude/skills/new-branch/SKILL.md)
for the full branch workflow.
## GitHub sync guard
When refreshing this repository from an internal source archive, preserve
GitHub-only contributor and automation files. Do not overwrite `CLAUDE.md`,
`.claude/skills/*`, `CONTRIBUTING.md`, or `.github/*` workflow/template files
without checking [docs/github-sync.md](docs/github-sync.md).
## Storage three-piece set

View File

@ -91,7 +91,7 @@ internally.
git clone https://github.com/EverMind-AI/EverOS.git
cd EverOS
make install # deps + pre-commit hooks (one-stop dev setup)
everos init # write ./.env, then fill in the API key slots
everos init # write ~/.everos/everos.toml + ome.toml
make ci # verify: lint + unit + integration + package
```
@ -153,7 +153,7 @@ Full conventions: [.claude/rules/testing.md](.claude/rules/testing.md).
- `/new-branch` — create branch with proper naming
- `/commit` — generate a Conventional Commits message
- `/pr`internal merge request with correct target branch
- `/pr`open a GitHub pull request with the correct target branch
---

View File

@ -1,4 +1,4 @@
.PHONY: help install install-deps lint docs-check check-commits check-pr-title check-assets check-deprecated-names check-cjk check-datetime openapi check-openapi format test integration package cov ci clean
.PHONY: help install install-deps lint docs-check check-commits check-pr-title check-assets check-deprecated-names check-github-docs check-cjk check-datetime openapi check-openapi format test integration package cov ci clean
help:
@echo "Targets:"
@ -10,6 +10,7 @@ help:
@echo " check-pr-title Validate PR title uses Conventional Commit format"
@echo " check-assets Block committed images, videos, and asset/media directories"
@echo " check-deprecated-names Block deprecated product names"
@echo " check-github-docs Block legacy/internal branch-model residue in contributor docs"
@echo " check-cjk Scan for CJK outside the language-policy allowlist (advisory)"
@echo " check-datetime Scan for code that bypasses component/utils/datetime (HARD gate, run via lint)"
@echo " openapi Regenerate docs/openapi.json from the FastAPI app"
@ -39,11 +40,13 @@ lint:
uv run lint-imports
uv run python scripts/check_repo_assets.py
uv run python scripts/check_deprecated_names.py
uv run python scripts/check_github_contributor_docs.py
uv run python scripts/check_datetime_discipline.py
uv run python scripts/dump_openapi.py --check
docs-check:
python3 scripts/check_docs.py
python3 scripts/check_github_contributor_docs.py
ruby -e 'require "yaml"; Dir[".github/ISSUE_TEMPLATE/*.yml"].sort.each { |p| YAML.load_file(p); puts "YAML ok: #{p}" }'
check-commits:
@ -61,6 +64,11 @@ check-assets:
check-deprecated-names:
uv run python scripts/check_deprecated_names.py
# GitHub contributor-doc gate. Public contribution guidance must target the
# GitHub `main` workflow, not an internal branch model.
check-github-docs:
uv run python scripts/check_github_contributor_docs.py
# Advisory CJK scan (see .claude/rules/language-policy.md). Deliberately NOT
# wired into `lint` / `ci`: the policy is enforced by review and the rules
# doc, not a hard gate. Run on demand when touching potentially-CJK files.

View File

@ -10,15 +10,19 @@ front of your agent.
## Prerequisites
- **Python 3.12+**
- **An OpenRouter API key** — covers the chat LLM (memory extraction)
*and* the multimodal LLM (parsing image / pdf / audio content items)
with a single key.
- **A DeepInfra API key** — for the embedding + rerank models that
OpenRouter doesn't ship.
- **An LLM provider key and endpoint** — for memory extraction. OpenRouter,
OpenAI, and other OpenAI-compatible providers work when you set
`base_url`.
- **A multimodal provider key and endpoint** — needed only when parsing
image / pdf / audio content items.
- **Embedding and rerank provider keys and endpoints** — for search. DeepInfra
works for the embedding + rerank path; vLLM and DashScope are also supported
for rerank.
Two keys total. Any OpenAI-compatible endpoint plugs in via the
matching `*__BASE_URL` env var if you'd rather use OpenAI directly,
self-host vLLM, route to Ollama, etc.
Many deployments use two distinct keys by reusing one LLM key for `[llm]` and
`[multimodal]`, and one DeepInfra key for `[embedding]` and `[rerank]`. Any
setting can live in TOML or be overridden by the matching `EVEROS_*`
environment variable.
## 1. Install
@ -29,15 +33,15 @@ pip install everos
## 2. Configure
Generate the starter config and drop in your two keys:
Generate the starter config and fill in provider credentials:
```bash
everos init # writes ~/.everos/everos.toml + ome.toml (use --root to relocate)
# Edit ~/.everos/everos.toml and fill four api_key slots (only two distinct keys needed):
# [llm] api_key (OpenRouter — chat LLM)
# [multimodal] api_key (OpenRouter — same key works)
# [embedding] api_key (DeepInfra)
# [rerank] api_key (DeepInfra — same key works)
# Edit ~/.everos/everos.toml and fill the provider fields:
# [llm] api_key + base_url (chat LLM)
# [multimodal] api_key + base_url (optional parser LLM)
# [embedding] model + api_key + base_url
# [rerank] model + api_key + base_url (provider can be inferred or set)
```
`everos init` generates two files: `everos.toml` (provider settings)
@ -48,9 +52,9 @@ after editing: `chmod 600 ~/.everos/everos.toml`.
The shipped template sets model defaults for `[llm]` (`gpt-4.1-mini`) and
`[multimodal]` (`google/gemini-3-flash-preview`); `[embedding]` and
`[rerank]` ship no model default — set `model` + `base_url` for those two
sections yourself (e.g. DeepInfra's `Qwen/Qwen3-Embedding-4B` /
`Qwen/Qwen3-Reranker-4B`). To use a different OpenAI-compatible endpoint
for any provider, set the matching `base_url` field.
sections yourself (for example DeepInfra's `Qwen/Qwen3-Embedding-4B` /
`Qwen/Qwen3-Reranker-4B`). `[llm]` and `[multimodal]` ship model defaults,
but still need `api_key` and `base_url` before those capabilities are used.
> **Where config lives**`everos init` writes into the memory root
> (`~/.everos` by default; relocate with `everos init --root <path>` and

View File

@ -2,7 +2,7 @@
## Supported Versions
EverOS is released and at v1.0.0 (stable). Security fixes are applied to the
EverOS is released and in the v1.x stable line. Security fixes are applied to the
latest release line only.
| Version | Supported |
@ -39,8 +39,9 @@ following in mind:
is loopback-only. Only set the bind to `0.0.0.0` (or any routable interface)
after you have placed your own gateway / auth layer in front;
`everos server start` will log a warning when you bind to `0.0.0.0`.
- Secrets (LLM / embedding API keys) live in your local `.env`; protect that
file as you would any credential. EverOS never transmits them anywhere except
the providers you configure.
- Secrets (LLM / embedding / rerank API keys) normally live in your local
`<root>/everos.toml`, or in `EVEROS_*` environment variables for container
deployments. Protect those values as you would any credential. EverOS never
transmits them anywhere except the providers you configure.
- Memory content is stored as plaintext `.md` files; apply OS-level file
permissions or disk encryption if your data is sensitive.

View File

@ -105,7 +105,7 @@ everos init --root /data/everos
|---|---|---|---|---|
| `model` | string | `"gpt-4.1-mini"` | No | LLM model identifier. |
| `api_key` | string | — | **Yes** | API key for the LLM provider. |
| `base_url` | string | — | No | Custom endpoint URL (OpenAI-compatible). |
| `base_url` | string | — | **Yes** | Endpoint URL (OpenAI-compatible). |
### `[multimodal]`
@ -113,7 +113,7 @@ everos init --root /data/everos
|---|---|---|---|---|
| `model` | string | `"google/gemini-3-flash-preview"` | No | Multimodal parsing model. |
| `api_key` | string | — | **Yes** | API key. |
| `base_url` | string | — | No | Custom endpoint URL. |
| `base_url` | string | — | **Yes** | Endpoint URL. |
| `max_concurrency` | int | `4` | No | Max parallel parsing requests. |
| `file_uri_allow_dirs` | list[string] | `[]` | No | Allowlisted base dirs for `file://` URIs. Empty = allow any readable file. |
| `file_uri_max_bytes` | int | `52428800` | No | Max size (bytes) of a `file://` asset; larger files are rejected. |
@ -134,7 +134,7 @@ everos init --root /data/everos
| Field | Type | Default | Required | Description |
|---|---|---|---|---|
| `provider` | string | `"deepinfra"` | No | Rerank provider: `deepinfra` or `vllm`. |
| `provider` | string | inferred | No | Rerank provider: `deepinfra`, `vllm`, or `dashscope`. |
| `model` | string | — | **Yes** | Reranker model identifier. |
| `api_key` | string | — | **Yes** | API key. |
| `base_url` | string | — | **Yes** | Rerank endpoint URL. |
@ -210,9 +210,13 @@ variables directly:
ENV EVEROS_ROOT=/data/everos
ENV EVEROS_LLM__API_KEY=sk-...
ENV EVEROS_LLM__MODEL=gpt-4o
ENV EVEROS_LLM__BASE_URL=https://api.openai.com/v1
ENV EVEROS_EMBEDDING__MODEL=text-embedding-3-large
ENV EVEROS_EMBEDDING__API_KEY=sk-...
ENV EVEROS_EMBEDDING__BASE_URL=https://api.openai.com/v1
ENV EVEROS_RERANK__MODEL=Qwen/Qwen3-Reranker-4B
ENV EVEROS_RERANK__API_KEY=...
ENV EVEROS_RERANK__BASE_URL=https://api.deepinfra.com/v1/inference
ENV EVEROS_API__HOST=0.0.0.0
ENV EVEROS_API__PORT=8000
```

77
docs/github-sync.md Normal file
View File

@ -0,0 +1,77 @@
# GitHub Sync Guard
This page records the GitHub-only files that must be preserved when refreshing
this public repository from the internal GitLab source/export.
## Rule
When a GitLab export, archive, rsync, or scripted copy is used to refresh the
GitHub repository, do **not** blindly overwrite GitHub-only contributor,
automation, and public-facing workflow files.
These files describe the public GitHub workflow: protected `main`, scoped
branches, GitHub pull requests, GitHub Actions, and community contribution
rules. Internal workflow text can mislead contributors and AI coding tools if
it is copied into GitHub.
## Preserve These GitHub Files
Keep the GitHub versions of these files unless a maintainer deliberately updates
them for the public GitHub workflow:
- `CLAUDE.md`
- `CONTRIBUTING.md`
- `.github/PULL_REQUEST_TEMPLATE.md`
- `.github/BRANCH_PROTECTION.md`
- `.github/workflows/ci.yml`
- `.github/workflows/docs.yml`
- `.github/workflows/commits.yml`
- `.github/ISSUE_TEMPLATE/**`
- `.claude/skills/commit/SKILL.md`
- `.claude/skills/new-branch/SKILL.md`
- `.claude/skills/pr/SKILL.md`
- `scripts/check_github_contributor_docs.py`
- `tests/unit/test_scripts/test_check_github_contributor_docs.py`
## Safe Sync Pattern
Prefer an explicit exclude list in the sync job rather than manual cleanup after
the overwrite. Example shape:
```bash
rsync -a --delete \
--exclude 'CLAUDE.md' \
--exclude 'CONTRIBUTING.md' \
--exclude '.github/PULL_REQUEST_TEMPLATE.md' \
--exclude '.github/BRANCH_PROTECTION.md' \
--exclude '.github/workflows/ci.yml' \
--exclude '.github/workflows/docs.yml' \
--exclude '.github/workflows/commits.yml' \
--exclude '.github/ISSUE_TEMPLATE/' \
--exclude '.claude/skills/commit/SKILL.md' \
--exclude '.claude/skills/new-branch/SKILL.md' \
--exclude '.claude/skills/pr/SKILL.md' \
--exclude 'scripts/check_github_contributor_docs.py' \
--exclude 'tests/unit/test_scripts/test_check_github_contributor_docs.py' \
<gitlab-export>/ <github-checkout>/
```
After any sync, run:
```bash
make docs-check
make lint
```
The `check_github_contributor_docs.py` gate catches the known failure mode:
public contributor docs drifting back to an internal branch/review model.
## Review Checklist
Before opening a sync PR:
- Confirm `CLAUDE.md` says branches are created from `main`.
- Confirm `.claude/skills/pr/SKILL.md` creates PRs with `--base main`.
- Confirm `CONTRIBUTING.md` says GitHub pull request, not internal review
terminology.
- Confirm `make docs-check` and `make lint` both pass.

View File

@ -50,6 +50,7 @@ specific thing (drain a queue, recover from a stuck row, etc.).
| Doc | Purpose |
|---|---|
| [cascade_runbook.md](cascade_runbook.md) | Cascade subsystem ops — drain queue, recover stuck rows |
| [github-sync.md](github-sync.md) | Guardrails for refreshing GitHub from internal exports without overwriting GitHub-only workflow files |
| [locomo_benchmark.md](locomo_benchmark.md) | LoCoMo benchmark — run and evaluate |
## Engineering

View File

@ -147,7 +147,7 @@ Ruminer brings persistent memory to a browser agent so it can carry personal con
#### EverMem Sync with EverOS
One command to connect any AI coding CLI to EverMemOS long-term memory.
One command to connect any AI coding CLI to EverOS long-term memory.
[Code](https://github.com/nanxingw/EverMem)

View File

@ -0,0 +1,133 @@
"""Block legacy/internal branch-model residue in contributor guidance docs."""
from __future__ import annotations
import re
from collections.abc import Iterable
from dataclasses import dataclass
from pathlib import Path
MONITORED_FILES = (
Path("CLAUDE.md"),
Path("CONTRIBUTING.md"),
Path("docs/engineering.md"),
Path(".github/PULL_REQUEST_TEMPLATE.md"),
Path(".claude/skills/commit/SKILL.md"),
Path(".claude/skills/new-branch/SKILL.md"),
Path(".claude/skills/pr/SKILL.md"),
)
BRANCH_WORKFLOW_FILES = frozenset(
{
Path("CLAUDE.md"),
Path(".claude/skills/commit/SKILL.md"),
Path(".claude/skills/new-branch/SKILL.md"),
Path(".claude/skills/pr/SKILL.md"),
}
)
@dataclass(frozen=True)
class Rule:
label: str
pattern: re.Pattern[str]
paths: frozenset[Path] | None = None
@dataclass(frozen=True)
class Violation:
path: Path
line_number: int
label: str
text: str
_LEGACY_HOSTING = "git" + "lab"
_LEGACY_STABLE_BRANCH = "mas" + "ter"
_LEGACY_INTEGRATION_BRANCH = "de" + "v"
_LEGACY_EMERGENCY_BRANCH = "hot" + "fix"
_LEGACY_BRANCH_MODEL = "git" + "flow"
RULES = (
Rule(
"legacy hosting reference",
re.compile(rf"\b{_LEGACY_HOSTING}\b", re.IGNORECASE),
),
Rule(
"legacy review wording",
re.compile(r"\bmerge\s+request\b", re.IGNORECASE),
),
Rule(
"legacy branch model",
re.compile(rf"\b{_LEGACY_BRANCH_MODEL}\b", re.IGNORECASE),
),
Rule(
"legacy stable branch reference",
re.compile(rf"\b{_LEGACY_STABLE_BRANCH}\b", re.IGNORECASE),
),
Rule(
"legacy integration branch reference",
re.compile(
rf"(?<![A-Za-z]){_LEGACY_INTEGRATION_BRANCH}(?![A-Za-z])",
re.IGNORECASE,
),
paths=BRANCH_WORKFLOW_FILES,
),
Rule(
"legacy emergency branch model",
re.compile(rf"\b{_LEGACY_EMERGENCY_BRANCH}\b", re.IGNORECASE),
paths=BRANCH_WORKFLOW_FILES,
),
)
def find_violations(
file_texts: Iterable[tuple[Path, str]],
) -> list[Violation]:
violations: list[Violation] = []
for path, text in file_texts:
for line_number, line in enumerate(text.splitlines(), start=1):
for rule in RULES:
if rule.paths is not None and path not in rule.paths:
continue
if rule.pattern.search(line):
violations.append(
Violation(
path=path,
line_number=line_number,
label=rule.label,
text=line.strip(),
)
)
return violations
def _read_monitored_files() -> list[tuple[Path, str]]:
return [
(path, path.read_text(encoding="utf-8"))
for path in MONITORED_FILES
if path.exists()
]
def main() -> int:
violations = find_violations(_read_monitored_files())
if not violations:
print("GitHub contributor-doc check passed.")
return 0
print(
"GitHub contributor-doc check failed.\n"
"These files should describe the public GitHub workflow: protected "
"`main`, scoped branches, and pull requests back to `main`.\n"
)
for violation in violations:
print(
f"- {violation.path}:{violation.line_number}: "
f"{violation.label}: {violation.text}"
)
return 1
if __name__ == "__main__":
raise SystemExit(main())

View File

@ -131,6 +131,14 @@ vector_strategy = "maxsim_atomic"
# requires a restart. Override via EVEROS_MEMORIZE__MODE.
mode = "agent"
# Maximum wall-clock for one memorize() invocation while holding the
# per-session lock. On timeout the outer asyncio.timeout cancels the call
# and the lock auto-releases so subsequent concurrent /add on the same
# session aren't deadlocked. Covers boundary LLM + memcell writes +
# synchronous portion of pipeline dispatch.
# Override via EVEROS_MEMORIZE__SESSION_LOCK_TIMEOUT_SECONDS.
session_lock_timeout_seconds = 360.0
[knowledge]
# Max bytes for an uploaded knowledge document (default 50 MiB). Oversized
# uploads are rejected with HTTP 422 before parsing/extraction. Note: the
@ -147,14 +155,6 @@ lambda = 0.1
mass_top_m = 50
top_k_cap = 100
# Maximum wall-clock for one memorize() invocation while holding the
# per-session lock. On timeout the outer asyncio.timeout cancels the call
# and the lock auto-releases so subsequent concurrent /add on the same
# session aren't deadlocked. Covers boundary LLM + memcell writes +
# synchronous portion of pipeline dispatch.
# Override via EVEROS_MEMORIZE__SESSION_LOCK_TIMEOUT_SECONDS.
session_lock_timeout_seconds = 360.0
[clustering]
# Geometry-clustering: cosine similarity threshold and time window.
# Episodes older than ``time_window_days`` from the newest cluster

View File

@ -0,0 +1,74 @@
"""Self-tests for ``scripts/check_github_contributor_docs.py``."""
from __future__ import annotations
import importlib.util
import sys
from pathlib import Path
_REPO_ROOT = Path(__file__).resolve().parents[3]
_CHECKER_PATH = _REPO_ROOT / "scripts" / "check_github_contributor_docs.py"
def _load_checker():
assert _CHECKER_PATH.exists(), "GitHub contributor-doc checker should exist"
spec = importlib.util.spec_from_file_location(
"_github_contributor_docs_checker", _CHECKER_PATH
)
assert spec and spec.loader
mod = importlib.util.module_from_spec(spec)
sys.modules[spec.name] = mod
spec.loader.exec_module(mod)
return mod
def test_clean_github_workflow_is_allowed() -> None:
checker = _load_checker()
violations = checker.find_violations(
[
(
Path("CLAUDE.md"),
"Create scoped branches from main and open pull requests.\n",
)
]
)
assert violations == []
def test_legacy_branch_model_terms_are_blocked() -> None:
checker = _load_checker()
stable_branch = "mas" + "ter"
integration_branch = "de" + "v"
legacy_review = "merge " + "request"
branch_model = "Git" + "Flow"
emergency_branch = "hot" + "fix"
violations = checker.find_violations(
[
(
Path("CLAUDE.md"),
f"{stable_branch} is stable and {integration_branch} is integration.\n",
),
(Path("CONTRIBUTING.md"), f"Open an internal {legacy_review}.\n"),
(
Path(".claude/skills/pr/SKILL.md"),
f"{branch_model} {emergency_branch} path.\n",
),
]
)
assert [(v.path.as_posix(), v.label) for v in violations] == [
("CLAUDE.md", "legacy stable branch reference"),
("CLAUDE.md", "legacy integration branch reference"),
("CONTRIBUTING.md", "legacy review wording"),
(".claude/skills/pr/SKILL.md", "legacy branch model"),
(".claude/skills/pr/SKILL.md", "legacy emergency branch model"),
]
def test_real_repo_has_no_legacy_contributor_doc_residue() -> None:
checker = _load_checker()
assert checker.main() == 0