codeforerunner 0.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. codeforerunner-0.3.0/LICENSE.md +71 -0
  2. codeforerunner-0.3.0/PKG-INFO +120 -0
  3. codeforerunner-0.3.0/README.md +106 -0
  4. codeforerunner-0.3.0/pyproject.toml +32 -0
  5. codeforerunner-0.3.0/setup.cfg +4 -0
  6. codeforerunner-0.3.0/src/codeforerunner/__init__.py +1 -0
  7. codeforerunner-0.3.0/src/codeforerunner/check.py +156 -0
  8. codeforerunner-0.3.0/src/codeforerunner/cli.py +236 -0
  9. codeforerunner-0.3.0/src/codeforerunner/config.py +176 -0
  10. codeforerunner-0.3.0/src/codeforerunner/doctor.py +321 -0
  11. codeforerunner-0.3.0/src/codeforerunner/installer.py +304 -0
  12. codeforerunner-0.3.0/src/codeforerunner/mcp_server.py +177 -0
  13. codeforerunner-0.3.0/src/codeforerunner/providers/__init__.py +36 -0
  14. codeforerunner-0.3.0/src/codeforerunner/providers/anthropic.py +61 -0
  15. codeforerunner-0.3.0/src/codeforerunner/providers/base.py +31 -0
  16. codeforerunner-0.3.0/src/codeforerunner/providers/google.py +62 -0
  17. codeforerunner-0.3.0/src/codeforerunner/providers/ollama.py +56 -0
  18. codeforerunner-0.3.0/src/codeforerunner/providers/openai.py +59 -0
  19. codeforerunner-0.3.0/src/codeforerunner.egg-info/PKG-INFO +120 -0
  20. codeforerunner-0.3.0/src/codeforerunner.egg-info/SOURCES.txt +34 -0
  21. codeforerunner-0.3.0/src/codeforerunner.egg-info/dependency_links.txt +1 -0
  22. codeforerunner-0.3.0/src/codeforerunner.egg-info/entry_points.txt +2 -0
  23. codeforerunner-0.3.0/src/codeforerunner.egg-info/requires.txt +1 -0
  24. codeforerunner-0.3.0/src/codeforerunner.egg-info/top_level.txt +1 -0
  25. codeforerunner-0.3.0/tests/test_check.py +197 -0
  26. codeforerunner-0.3.0/tests/test_check_config_integration.py +45 -0
  27. codeforerunner-0.3.0/tests/test_cli.py +238 -0
  28. codeforerunner-0.3.0/tests/test_config.py +127 -0
  29. codeforerunner-0.3.0/tests/test_doctor.py +137 -0
  30. codeforerunner-0.3.0/tests/test_examples.py +23 -0
  31. codeforerunner-0.3.0/tests/test_hooks_manifest.py +31 -0
  32. codeforerunner-0.3.0/tests/test_installer.py +229 -0
  33. codeforerunner-0.3.0/tests/test_mcp_server.py +216 -0
  34. codeforerunner-0.3.0/tests/test_providers.py +146 -0
  35. codeforerunner-0.3.0/tests/test_validate_codex_marketplace.py +105 -0
  36. codeforerunner-0.3.0/tests/test_workflows_yaml.py +99 -0
@@ -0,0 +1,71 @@
1
+ Codeforerunner Source-Available License v0.1
2
+ ===========================================
3
+
4
+ Copyright (c) 2026 Derek Palmer
5
+
6
+ 1. Grant of rights
7
+
8
+ Subject to the terms of this license, you are permitted to:
9
+
10
+ 1.1 Use this software for personal, educational, and commercial purposes.
11
+
12
+ 1.2 Modify this software for your own internal use, including within your
13
+ personal projects, your employer's internal systems, or client work,
14
+ provided that the modified software is not itself sold, licensed,
15
+ hosted as a commercial offering, or otherwise offered as a product or
16
+ service to third parties.
17
+
18
+ 1.3 Redistribute unmodified copies of this software, provided that you
19
+ include this license and all copyright notices.
20
+
21
+ 2. Restrictions
22
+
23
+ You may NOT, under any circumstance:
24
+
25
+ 2.1 Sell, license, sublicense, host, distribute for a fee, or otherwise
26
+ offer this software, or any modified version of it, as a standalone
27
+ product, hosted product, or primary service offering.
28
+
29
+ 2.2 Modify, adapt, extend, or integrate this software into another
30
+ product or service and then sell, license, sublicense, host, or
31
+ otherwise commercially offer that product or service where this
32
+ software or any derivative of it provides material functionality.
33
+
34
+ 2.3 Rebrand this software, or any modified version of it, as your own
35
+ competing product, or present it in a way that suggests it is your
36
+ original creation.
37
+
38
+ 2.4 Remove or alter any attribution, copyright, or license notices
39
+ included with the software, except as necessary to add your own,
40
+ clearly subordinate notices for internal tracking.
41
+
42
+ 3. No trademark license
43
+
44
+ The names "codeforerunner", "forerunner", and any associated project
45
+ logos or branding are not licensed for use as your own product or
46
+ service names. You may refer to the software by its name in truthful
47
+ descriptions, such as "powered by codeforerunner", but you may not use
48
+ these names or logos in a way that suggests official endorsement or
49
+ affiliation without prior written permission from the copyright holder.
50
+
51
+ 4. No warranty
52
+
53
+ THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
54
+ OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
55
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
56
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
57
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
58
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
59
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
60
+
61
+ 5. Termination
62
+
63
+ Any violation of this license automatically terminates your rights under
64
+ it. Upon termination, you must stop using and distributing the software
65
+ and any derivative works, except where otherwise allowed by applicable
66
+ law.
67
+
68
+ 6. Miscellaneous
69
+
70
+ If any provision of this license is held to be unenforceable, the
71
+ remaining provisions will remain in full force and effect.
@@ -0,0 +1,120 @@
1
+ Metadata-Version: 2.4
2
+ Name: codeforerunner
3
+ Version: 0.3.0
4
+ Summary: Model-agnostic repository documentation tooling (prompt-first; thin CLI).
5
+ Author: Derek Palmer
6
+ License-Expression: LicenseRef-Codeforerunner-SAL-0.1
7
+ Project-URL: Repository, https://github.com/derek-palmer/codeforerunner
8
+ Project-URL: Issues, https://github.com/derek-palmer/codeforerunner/issues
9
+ Requires-Python: >=3.11
10
+ Description-Content-Type: text/markdown
11
+ License-File: LICENSE.md
12
+ Requires-Dist: PyYAML>=6.0
13
+ Dynamic: license-file
14
+
15
+ ![codeForerunner — your codebase gets a Forerunner; your docs finally see the light](images/readme_banner.png)
16
+
17
+ # codeForerunner
18
+
19
+ CodeForerunner is a model-agnostic documentation agent that acts as overwatch for your repository, automatically analyzing code and maintaining docs, diagrams, and architecture knowledge as your codebase evolves over time.
20
+
21
+ The current repo is the prompt-first foundation for that agent: it ships prompt assets for understanding a codebase and generating developer docs. A thin Python CLI (including `forerunner mcp-server` and a scoped `forerunner init --full / --agents-only`), an idempotent skill installer, pre-commit + CI hooks, and a PyPI publish workflow now wrap those prompts; the first published PyPI release remains pending.
22
+
23
+ ## Current State
24
+
25
+ - Core product: Markdown prompts in `prompts/`.
26
+ - Agent package artifacts: Codex plugin files under `plugins/codeforerunner/` and Claude Code plugin files under `.claude-plugin/` plus `skills/codeforerunner/`.
27
+ - Python package: `pyproject.toml` + `src/codeforerunner/` expose a `forerunner` console script. `forerunner doc <task>` resolves the prompt bundle (base + partials + task) to stdout; `forerunner install <agent>` idempotently writes the canonical skill into agent-specific directories; `forerunner init` resolves the agent-onboarding bundle (with `--full` to prepend a scan or `--agents-only` for the default scope); `forerunner scan` resolves the scan bundle; `forerunner mcp-server` serves prompt bundles as MCP tools over stdio.
28
+ - Hooks: `.pre-commit-hooks.yaml` exposes a `forerunner-check` hook; `.github/workflows/forerunner-check.yml` mirrors it in CI. Both no-op when `forerunner.config.yaml` is absent.
29
+ - Current config: `forerunner.config.yaml.example` documents the schema now parsed by `src/codeforerunner/config.py`; see "Configuration" below.
30
+ - Not currently present: Docker image, Makefile, published PyPI release.
31
+
32
+ ## Install
33
+
34
+ After the first PyPI release:
35
+
36
+ ```bash
37
+ pipx install codeforerunner # recommended; isolated environment
38
+ pip install codeforerunner # alternative
39
+ ```
40
+
41
+ From source:
42
+
43
+ ```bash
44
+ git clone https://github.com/derek-palmer/codeForerunner
45
+ cd codeForerunner
46
+ python -m pip install -e .
47
+ ```
48
+
49
+ Then `forerunner --help` should print the subcommand list.
50
+
51
+ ## Prompt Layout
52
+
53
+ ```text
54
+ prompts/
55
+ ├── system/
56
+ │ └── base.md
57
+ ├── partials/
58
+ │ ├── context-format.md
59
+ │ ├── output-rules.md
60
+ │ └── stack-hints.md
61
+ └── tasks/
62
+ ├── scan.md
63
+ ├── init-agent-onboarding.md
64
+ ├── readme.md
65
+ ├── api-docs.md
66
+ ├── stack-docs.md
67
+ ├── diagrams.md
68
+ ├── flows.md
69
+ ├── version-audit.md
70
+ ├── check.md
71
+ └── review.md
72
+ ```
73
+
74
+ ## Quick Start
75
+
76
+ 1. Open `prompts/system/base.md` and use it as the agent system or project instruction.
77
+ 2. Assemble repo context using the shape in `prompts/partials/context-format.md`.
78
+ 3. For documentation generation, run `prompts/tasks/scan.md` first.
79
+ 4. For agent onboarding only, run `prompts/tasks/init-agent-onboarding.md` directly.
80
+ 5. Pass the scan result into one downstream documentation prompt, such as `prompts/tasks/readme.md` or `prompts/tasks/stack-docs.md`.
81
+ 6. Apply generated docs only after checking that every claim is grounded in provided files.
82
+
83
+ ## What The Prompts Do
84
+
85
+ | Prompt | Purpose |
86
+ | --- | --- |
87
+ | `prompts/system/base.md` | Defines the codeforerunner role, quality bar, Markdown rules, and accuracy constraints. |
88
+ | `prompts/tasks/scan.md` | Produces the first structured repo scan used by downstream tasks. |
89
+ | `prompts/tasks/init-agent-onboarding.md` | Generates or updates `AGENTS.md` from repo evidence plus files such as `CLAUDE.md`, `.cursor/rules/*`, `.cursorrules`, `.github/copilot-instructions.md`, and `opencode.json`. |
90
+ | `prompts/tasks/readme.md` | Generates or rewrites a top-level README from scan output and selected files. |
91
+ | `prompts/tasks/api-docs.md` | Documents public APIs when endpoints/interfaces are evident. |
92
+ | `prompts/tasks/stack-docs.md` | Documents stack-specific areas of a repo. |
93
+ | `prompts/tasks/diagrams.md` | Generates Mermaid architecture or flow diagrams. |
94
+ | `prompts/tasks/flows.md` | Documents user, request, job, or data flows. |
95
+ | `prompts/tasks/version-audit.md` | Audits pinned versions from manifests, lockfiles, Dockerfiles, workflows, or IaC. |
96
+ | `prompts/tasks/check.md` | Checks existing docs for staleness against a fresh scan. |
97
+ | `prompts/tasks/review.md` | Summarizes documentation impact for review. |
98
+
99
+ ## Docs And Spec
100
+
101
+ - `SPEC.md` tracks phases, invariants, and tasks so future PRs can make small status updates instead of broad rewrites.
102
+ - `docs/getting-started.md` explains manual prompt use.
103
+ - `docs/prompt-guide.md` explains how system, partial, and task prompts compose.
104
+ - `docs/editor-agent-setup.md` explains how to adapt prompts to local agents.
105
+ - `docs/roadmap.md` mirrors the `SPEC.md` phase status in human-readable form.
106
+ - `docs/agent-distribution-design.md` records the design that backs the Codex/Claude packages and `forerunner install`.
107
+
108
+ ## Configuration
109
+
110
+ `forerunner.config.yaml.example` documents the loaded schema. Copy it to `forerunner.config.yaml` to opt in; without that file, `forerunner check` is a silent no-op. The schema has top-level provider/model fields (`provider`, `model`, `api_key_env`, `output_dir`, `context_max_files`, `context_max_lines_per_file`, `approaching_eol_threshold_months`), `ignore_patterns`, `tasks.version_audit`, and `tasks.check`. `forerunner check` honors `tasks.check.enabled_rules` (allowlist of rule IDs, default all) and `tasks.check.ignore_paths` (fnmatch globs applied to scanned docs). Invalid YAML, unknown providers, unknown `api_key_env` providers, or unknown severities surface as a `ConfigError` and exit non-zero.
111
+
112
+ ### MCP Server
113
+
114
+ `forerunner mcp-server` speaks JSON-RPC 2.0 over stdio and exposes one tool per `prompts/tasks/*.md` (tool name = filename stem). Each `tools/call` returns the resolved `base + partials + task` bundle as text. A scan-first gate enforces SPEC V2: any tool other than `scan` or `init-agent-onboarding` returns an error until `scan` has been called in the same session. Point any MCP-compatible client at `forerunner mcp-server` as a stdio server (running from the target repo so `prompts/tasks/` resolves).
115
+
116
+ See `examples/mcp/` for Claude Desktop and mcp-cli wiring examples.
117
+
118
+ ## Roadmap
119
+
120
+ See `SPEC.md` for the canonical phase/task tracker and `docs/roadmap.md` for the human-readable roadmap.
@@ -0,0 +1,106 @@
1
+ ![codeForerunner — your codebase gets a Forerunner; your docs finally see the light](images/readme_banner.png)
2
+
3
+ # codeForerunner
4
+
5
+ CodeForerunner is a model-agnostic documentation agent that acts as overwatch for your repository, automatically analyzing code and maintaining docs, diagrams, and architecture knowledge as your codebase evolves over time.
6
+
7
+ The current repo is the prompt-first foundation for that agent: it ships prompt assets for understanding a codebase and generating developer docs. A thin Python CLI (including `forerunner mcp-server` and a scoped `forerunner init --full / --agents-only`), an idempotent skill installer, pre-commit + CI hooks, and a PyPI publish workflow now wrap those prompts; the first published PyPI release remains pending.
8
+
9
+ ## Current State
10
+
11
+ - Core product: Markdown prompts in `prompts/`.
12
+ - Agent package artifacts: Codex plugin files under `plugins/codeforerunner/` and Claude Code plugin files under `.claude-plugin/` plus `skills/codeforerunner/`.
13
+ - Python package: `pyproject.toml` + `src/codeforerunner/` expose a `forerunner` console script. `forerunner doc <task>` resolves the prompt bundle (base + partials + task) to stdout; `forerunner install <agent>` idempotently writes the canonical skill into agent-specific directories; `forerunner init` resolves the agent-onboarding bundle (with `--full` to prepend a scan or `--agents-only` for the default scope); `forerunner scan` resolves the scan bundle; `forerunner mcp-server` serves prompt bundles as MCP tools over stdio.
14
+ - Hooks: `.pre-commit-hooks.yaml` exposes a `forerunner-check` hook; `.github/workflows/forerunner-check.yml` mirrors it in CI. Both no-op when `forerunner.config.yaml` is absent.
15
+ - Current config: `forerunner.config.yaml.example` documents the schema now parsed by `src/codeforerunner/config.py`; see "Configuration" below.
16
+ - Not currently present: Docker image, Makefile, published PyPI release.
17
+
18
+ ## Install
19
+
20
+ After the first PyPI release:
21
+
22
+ ```bash
23
+ pipx install codeforerunner # recommended; isolated environment
24
+ pip install codeforerunner # alternative
25
+ ```
26
+
27
+ From source:
28
+
29
+ ```bash
30
+ git clone https://github.com/derek-palmer/codeForerunner
31
+ cd codeForerunner
32
+ python -m pip install -e .
33
+ ```
34
+
35
+ Then `forerunner --help` should print the subcommand list.
36
+
37
+ ## Prompt Layout
38
+
39
+ ```text
40
+ prompts/
41
+ ├── system/
42
+ │ └── base.md
43
+ ├── partials/
44
+ │ ├── context-format.md
45
+ │ ├── output-rules.md
46
+ │ └── stack-hints.md
47
+ └── tasks/
48
+ ├── scan.md
49
+ ├── init-agent-onboarding.md
50
+ ├── readme.md
51
+ ├── api-docs.md
52
+ ├── stack-docs.md
53
+ ├── diagrams.md
54
+ ├── flows.md
55
+ ├── version-audit.md
56
+ ├── check.md
57
+ └── review.md
58
+ ```
59
+
60
+ ## Quick Start
61
+
62
+ 1. Open `prompts/system/base.md` and use it as the agent system or project instruction.
63
+ 2. Assemble repo context using the shape in `prompts/partials/context-format.md`.
64
+ 3. For documentation generation, run `prompts/tasks/scan.md` first.
65
+ 4. For agent onboarding only, run `prompts/tasks/init-agent-onboarding.md` directly.
66
+ 5. Pass the scan result into one downstream documentation prompt, such as `prompts/tasks/readme.md` or `prompts/tasks/stack-docs.md`.
67
+ 6. Apply generated docs only after checking that every claim is grounded in provided files.
68
+
69
+ ## What The Prompts Do
70
+
71
+ | Prompt | Purpose |
72
+ | --- | --- |
73
+ | `prompts/system/base.md` | Defines the codeforerunner role, quality bar, Markdown rules, and accuracy constraints. |
74
+ | `prompts/tasks/scan.md` | Produces the first structured repo scan used by downstream tasks. |
75
+ | `prompts/tasks/init-agent-onboarding.md` | Generates or updates `AGENTS.md` from repo evidence plus files such as `CLAUDE.md`, `.cursor/rules/*`, `.cursorrules`, `.github/copilot-instructions.md`, and `opencode.json`. |
76
+ | `prompts/tasks/readme.md` | Generates or rewrites a top-level README from scan output and selected files. |
77
+ | `prompts/tasks/api-docs.md` | Documents public APIs when endpoints/interfaces are evident. |
78
+ | `prompts/tasks/stack-docs.md` | Documents stack-specific areas of a repo. |
79
+ | `prompts/tasks/diagrams.md` | Generates Mermaid architecture or flow diagrams. |
80
+ | `prompts/tasks/flows.md` | Documents user, request, job, or data flows. |
81
+ | `prompts/tasks/version-audit.md` | Audits pinned versions from manifests, lockfiles, Dockerfiles, workflows, or IaC. |
82
+ | `prompts/tasks/check.md` | Checks existing docs for staleness against a fresh scan. |
83
+ | `prompts/tasks/review.md` | Summarizes documentation impact for review. |
84
+
85
+ ## Docs And Spec
86
+
87
+ - `SPEC.md` tracks phases, invariants, and tasks so future PRs can make small status updates instead of broad rewrites.
88
+ - `docs/getting-started.md` explains manual prompt use.
89
+ - `docs/prompt-guide.md` explains how system, partial, and task prompts compose.
90
+ - `docs/editor-agent-setup.md` explains how to adapt prompts to local agents.
91
+ - `docs/roadmap.md` mirrors the `SPEC.md` phase status in human-readable form.
92
+ - `docs/agent-distribution-design.md` records the design that backs the Codex/Claude packages and `forerunner install`.
93
+
94
+ ## Configuration
95
+
96
+ `forerunner.config.yaml.example` documents the loaded schema. Copy it to `forerunner.config.yaml` to opt in; without that file, `forerunner check` is a silent no-op. The schema has top-level provider/model fields (`provider`, `model`, `api_key_env`, `output_dir`, `context_max_files`, `context_max_lines_per_file`, `approaching_eol_threshold_months`), `ignore_patterns`, `tasks.version_audit`, and `tasks.check`. `forerunner check` honors `tasks.check.enabled_rules` (allowlist of rule IDs, default all) and `tasks.check.ignore_paths` (fnmatch globs applied to scanned docs). Invalid YAML, unknown providers, unknown `api_key_env` providers, or unknown severities surface as a `ConfigError` and exit non-zero.
97
+
98
+ ### MCP Server
99
+
100
+ `forerunner mcp-server` speaks JSON-RPC 2.0 over stdio and exposes one tool per `prompts/tasks/*.md` (tool name = filename stem). Each `tools/call` returns the resolved `base + partials + task` bundle as text. A scan-first gate enforces SPEC V2: any tool other than `scan` or `init-agent-onboarding` returns an error until `scan` has been called in the same session. Point any MCP-compatible client at `forerunner mcp-server` as a stdio server (running from the target repo so `prompts/tasks/` resolves).
101
+
102
+ See `examples/mcp/` for Claude Desktop and mcp-cli wiring examples.
103
+
104
+ ## Roadmap
105
+
106
+ See `SPEC.md` for the canonical phase/task tracker and `docs/roadmap.md` for the human-readable roadmap.
@@ -0,0 +1,32 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "codeforerunner"
7
+ version = "0.3.0"
8
+ description = "Model-agnostic repository documentation tooling (prompt-first; thin CLI)."
9
+ readme = "README.md"
10
+ requires-python = ">=3.11"
11
+ license = "LicenseRef-Codeforerunner-SAL-0.1"
12
+ license-files = ["LICENSE.md"]
13
+ authors = [{ name = "Derek Palmer" }]
14
+ dependencies = [
15
+ "PyYAML>=6.0",
16
+ ]
17
+
18
+ [project.urls]
19
+ Repository = "https://github.com/derek-palmer/codeforerunner"
20
+ Issues = "https://github.com/derek-palmer/codeforerunner/issues"
21
+
22
+ [project.scripts]
23
+ forerunner = "codeforerunner.cli:main"
24
+
25
+ [tool.setuptools.packages.find]
26
+ where = ["src"]
27
+
28
+ [tool.setuptools.package-data]
29
+ codeforerunner = ["py.typed"]
30
+
31
+ [tool.pytest.ini_options]
32
+ testpaths = ["tests"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1 @@
1
+ __version__ = "0.2.0"
@@ -0,0 +1,156 @@
1
+ """Drift detection for docs that claim files don't exist when they do."""
2
+ from __future__ import annotations
3
+
4
+ import fnmatch
5
+ import re
6
+ from dataclasses import dataclass
7
+ from pathlib import Path
8
+
9
+ from codeforerunner.config import CheckConfig
10
+
11
+
12
+ @dataclass(frozen=True)
13
+ class Violation:
14
+ path: Path
15
+ line: int
16
+ rule_id: str
17
+ message: str
18
+
19
+
20
+ _RULES = [
21
+ (
22
+ "R1-no-cli",
23
+ re.compile(
24
+ r"(?i)no\s+CLI\s+exists"
25
+ r"|CLI\s+does\s+not\s+exist"
26
+ r"|not\s+currently\s+present:[^.]*\bCLI\b"
27
+ r"|Do\s+not\s+run\s+`forerunner`"
28
+ ),
29
+ ("src/codeforerunner/cli.py",),
30
+ "doc claims no CLI exists, but src/codeforerunner/cli.py is present",
31
+ ),
32
+ (
33
+ "R2-no-pre-commit",
34
+ re.compile(r"(?i)no\s+pre[- ]commit(\s+hook)?"),
35
+ (".pre-commit-hooks.yaml",),
36
+ "doc claims no pre-commit hook, but .pre-commit-hooks.yaml is present",
37
+ ),
38
+ (
39
+ "R3-no-ci",
40
+ re.compile(r"(?i)no\s+CI(\s+workflow)?"),
41
+ (".github/workflows/*.yml",),
42
+ "doc claims no CI workflow, but .github/workflows/*.yml is present",
43
+ ),
44
+ (
45
+ "R4-no-installer",
46
+ re.compile(r"(?i)no\s+installer"),
47
+ ("src/codeforerunner/installer.py",),
48
+ "doc claims no installer, but src/codeforerunner/installer.py is present",
49
+ ),
50
+ (
51
+ "R5-no-python-package",
52
+ re.compile(r"(?i)no\s+Python\s+package"),
53
+ ("pyproject.toml",),
54
+ "doc claims no Python package, but pyproject.toml is present",
55
+ ),
56
+ (
57
+ "R6-no-docker",
58
+ re.compile(r"(?i)no\s+Docker(\s+image)?|no\s+Dockerfile"),
59
+ ("Dockerfile", "compose.yml", "docker-compose.yml"),
60
+ "doc claims no Docker, but Dockerfile/compose file is present",
61
+ ),
62
+ (
63
+ "R6b-no-makefile",
64
+ re.compile(r"(?i)no\s+Makefile"),
65
+ ("Makefile",),
66
+ "doc claims no Makefile, but Makefile is present",
67
+ ),
68
+ (
69
+ "R7-no-mcp",
70
+ re.compile(r"(?i)no\s+MCP(\s+server)?"),
71
+ ("src/codeforerunner/mcp_server.py",),
72
+ "doc claims no MCP server, but src/codeforerunner/mcp_server.py is present",
73
+ ),
74
+ (
75
+ "R8-no-marketplace",
76
+ re.compile(r"(?i)no\s+marketplace(\s+manifest)?"),
77
+ ("plugins/codex/marketplace.json",),
78
+ "doc claims no marketplace, but plugins/codex/marketplace.json is present",
79
+ ),
80
+ ]
81
+
82
+
83
+ def _trigger_exists(repo: Path, patterns: tuple[str, ...]) -> bool:
84
+ for pat in patterns:
85
+ if "*" in pat:
86
+ parent = repo / Path(pat).parent
87
+ name = Path(pat).name
88
+ if parent.is_dir() and any(parent.glob(name)):
89
+ return True
90
+ else:
91
+ if (repo / pat).exists():
92
+ return True
93
+ return False
94
+
95
+
96
+ def _scanned_docs(repo: Path) -> list[Path]:
97
+ docs: list[Path] = []
98
+ readme = repo / "README.md"
99
+ if readme.is_file():
100
+ docs.append(readme)
101
+ docs_dir = repo / "docs"
102
+ if docs_dir.is_dir():
103
+ docs.extend(sorted(p for p in docs_dir.rglob("*.md") if p.is_file()))
104
+ return docs
105
+
106
+
107
+ def _path_ignored(repo: Path, doc: Path, ignore_patterns: tuple[str, ...]) -> bool:
108
+ if not ignore_patterns:
109
+ return False
110
+ try:
111
+ rel = doc.relative_to(repo).as_posix()
112
+ except ValueError:
113
+ rel = doc.as_posix()
114
+ return any(fnmatch.fnmatch(rel, pat) for pat in ignore_patterns)
115
+
116
+
117
+ def run(repo: Path, config: CheckConfig | None = None) -> list[Violation]:
118
+ """Scan repo docs for drift; return list of violations.
119
+
120
+ `config` filters rules via `enabled_rules` and skips docs matching `ignore_paths`.
121
+ `None` config (default) preserves the pre-T25 behavior: all rules, no ignores.
122
+ """
123
+ repo = Path(repo)
124
+ enabled = set(config.enabled_rules) if (config and config.enabled_rules is not None) else None
125
+ ignore_patterns = config.ignore_paths if config else ()
126
+
127
+ active_rules = [
128
+ (rid, rx, msg)
129
+ for rid, rx, triggers, msg in _RULES
130
+ if _trigger_exists(repo, triggers) and (enabled is None or rid in enabled)
131
+ ]
132
+ if not active_rules:
133
+ return []
134
+
135
+ violations: list[Violation] = []
136
+ for doc in _scanned_docs(repo):
137
+ if _path_ignored(repo, doc, ignore_patterns):
138
+ continue
139
+ try:
140
+ text = doc.read_text(encoding="utf-8")
141
+ except (OSError, UnicodeDecodeError):
142
+ continue
143
+ for lineno, line in enumerate(text.splitlines(), start=1):
144
+ for rid, rx, msg in active_rules:
145
+ if rx.search(line):
146
+ violations.append(
147
+ Violation(path=doc, line=lineno, rule_id=rid, message=msg)
148
+ )
149
+ return violations
150
+
151
+
152
+ def format_violations(vs: list[Violation]) -> str:
153
+ """Format violations one per line for stderr."""
154
+ return "\n".join(
155
+ f"{v.path}:{v.line}: {v.rule_id}: {v.message}" for v in vs
156
+ )