Skip to content

Commit b9889c6

Browse files
ci: add CODEOWNERS, dependabot, CI/CD workflows (auto-PR, publish)
1 parent e1a42ee commit b9889c6

20 files changed

Lines changed: 679 additions & 22 deletions

.github/CODEOWNERS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
* @Coding-Dev-Tools

.github/dependabot.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
version: 2
2+
updates:
3+
- package-ecosystem: pip
4+
directory: "/"
5+
schedule:
6+
interval: weekly
7+
open-pull-requests-limit: 10
8+
9+
- package-ecosystem: github-actions
10+
directory: "/"
11+
schedule:
12+
interval: weekly

.github/workflows/ci.yml

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
jobs:
10+
lint:
11+
runs-on: ubuntu-latest
12+
steps:
13+
- uses: actions/checkout@v6
14+
15+
- name: Set up Python
16+
uses: actions/setup-python@v6
17+
with:
18+
python-version: "3.12"
19+
20+
- name: Install ruff
21+
run: pip install ruff
22+
23+
- name: Run ruff check
24+
run: ruff check src/ tests/
25+
26+
- name: Check formatting
27+
run: ruff format --check src/ tests/
28+
29+
test:
30+
runs-on: ubuntu-latest
31+
strategy:
32+
matrix:
33+
python-version: ["3.10", "3.11", "3.12", "3.13"]
34+
35+
steps:
36+
- uses: actions/checkout@v6
37+
38+
- name: Set up Python ${{ matrix.python-version }}
39+
uses: actions/setup-python@v6
40+
with:
41+
python-version: ${{ matrix.python-version }}
42+
43+
- name: Install dependencies
44+
run: |
45+
python -m pip install --upgrade pip
46+
pip install -e ".[dev]"
47+
48+
- name: Run tests
49+
run: |
50+
pytest tests/ -v --tb=short
51+
52+
- name: Check package build
53+
run: |
54+
pip install build twine
55+
python -m build
56+
twine check dist/*
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Seeded by the repo-improver-rotation Cowork job into cowork/improve-* branches.
2+
# Opens a PR automatically when such a branch is pushed (sandbox cannot reach
3+
# the GitHub API directly; this runs server-side with the repo's GITHUB_TOKEN).
4+
name: cowork-auto-pr
5+
on:
6+
push:
7+
branches: ['cowork/improve-**']
8+
permissions:
9+
contents: read
10+
pull-requests: write
11+
jobs:
12+
ensure-pr:
13+
runs-on: ubuntu-latest
14+
steps:
15+
- name: Open PR for this branch if none exists
16+
env:
17+
GH_TOKEN: ${{ github.token }}
18+
run: |
19+
set -eu
20+
existing=$(gh pr list --repo "$GITHUB_REPOSITORY" --head "$GITHUB_REF_NAME" --state open --json number --jq 'length')
21+
if [ "$existing" = "0" ]; then
22+
gh pr create --repo "$GITHUB_REPOSITORY" \
23+
--head "$GITHUB_REF_NAME" \
24+
--title "cowork-bot: automated improvements ($GITHUB_REF_NAME)" \
25+
--body "Automated improvement PR from the Cowork repo-improver rotation (one coherent senior-dev improvement per run; see individual commit messages). Subsequent runs push additional commits to this PR rather than opening new ones."
26+
else
27+
echo "Open PR already exists for $GITHUB_REF_NAME — nothing to do."
28+
fi

.github/workflows/publish.yml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
name: Publish to PyPI
2+
3+
on:
4+
release:
5+
types: [ published ]
6+
workflow_dispatch:
7+
8+
jobs:
9+
publish:
10+
runs-on: ubuntu-latest
11+
environment: pypi
12+
permissions:
13+
id-token: write
14+
15+
steps:
16+
- uses: actions/checkout@v6
17+
18+
- name: Set up Python
19+
uses: actions/setup-python@v6
20+
with:
21+
python-version: '3.12'
22+
23+
- name: Install dependencies
24+
run: |
25+
python -m pip install --upgrade pip
26+
pip install build twine
27+
28+
- name: Build package
29+
run: python -m build
30+
31+
- name: Check package
32+
run: twine check dist/*
33+
34+
- name: Publish to PyPI
35+
uses: pypa/gh-action-pypi-publish@release/v1
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
name: release-audit
2+
3+
on:
4+
pull_request:
5+
branches: [main, master]
6+
push:
7+
branches: [main, master]
8+
workflow_dispatch:
9+
10+
jobs:
11+
audit:
12+
runs-on: ubuntu-latest
13+
steps:
14+
- uses: actions/checkout@v6
15+
16+
- name: Set up Python
17+
uses: actions/setup-python@v6
18+
with:
19+
python-version: "3.11"
20+
21+
- name: Run release audit
22+
run: |
23+
python3 - <<'PY'
24+
import json, pathlib, sys
25+
26+
repo = pathlib.Path.cwd()
27+
scorecard = {"overall_grade": "A", "angles_passing": 0, "angles_total": 8, "blockers": 0, "angles": []}
28+
29+
def check(name, grade, detail):
30+
scorecard["angles"].append({"angle": name, "grade": grade, "detail": detail})
31+
if grade == "A":
32+
scorecard["angles_passing"] += 1
33+
elif grade == "F":
34+
scorecard["blockers"] += 1
35+
36+
# 1. README
37+
readme = repo / "README.md"
38+
if readme.exists() and len(readme.read_text()) > 200:
39+
check("README", "A", "README.md exists and has content")
40+
else:
41+
check("README", "F", "README.md missing or too short")
42+
43+
# 2. License
44+
lic = repo / "LICENSE"
45+
if lic.exists() and len(lic.read_text()) > 100:
46+
check("License", "A", "LICENSE file present")
47+
else:
48+
check("License", "F", "LICENSE file missing or too short")
49+
50+
# 3. CI/CD
51+
wf_dir = repo / ".github" / "workflows"
52+
wf_files = list(wf_dir.glob("*.yml")) + list(wf_dir.glob("*.yaml"))
53+
if len(wf_files) >= 1:
54+
check("CI/CD", "A", f"{len(wf_files)} workflow(s) found")
55+
else:
56+
check("CI/CD", "F", "No CI/CD workflows found")
57+
58+
# 4. Dependencies
59+
pyproj = repo / "pyproject.toml"
60+
if pyproj.exists():
61+
check("Dependencies", "A", "pyproject.toml found")
62+
else:
63+
check("Dependencies", "F", "pyproject.toml missing")
64+
65+
# 5. Tests
66+
tests = repo / "tests"
67+
test_files = list(tests.glob("test_*.py")) + list(tests.glob("*_test.py"))
68+
if len(test_files) >= 1:
69+
check("Tests", "A", f"{len(test_files)} test file(s) found")
70+
else:
71+
check("Tests", "F", "No test files found")
72+
73+
# 6. Versioning
74+
if pyproj.exists():
75+
import tomllib
76+
data = tomllib.loads(pyproj.read_text())
77+
ver = data.get("project", {}).get("version", "")
78+
if ver:
79+
check("Versioning", "A", f"Version {ver} set in pyproject.toml")
80+
else:
81+
check("Versioning", "F", "No version in pyproject.toml")
82+
else:
83+
check("Versioning", "F", "Cannot check version — pyproject.toml missing")
84+
85+
# 7. Changelog
86+
changelog = repo / "CHANGELOG.md"
87+
if changelog.exists() and len(changelog.read_text()) > 50:
88+
check("Changelog", "A", "CHANGELOG.md present")
89+
else:
90+
check("Changelog", "C", "CHANGELOG.md missing or too short")
91+
92+
# 8. Security
93+
sec = repo / "SECURITY.md"
94+
if sec.exists() and len(sec.read_text()) > 50:
95+
check("Security", "A", "SECURITY.md present")
96+
else:
97+
check("Security", "C", "SECURITY.md missing or too short")
98+
99+
# Compute overall grade
100+
if scorecard["blockers"] > 0:
101+
scorecard["overall_grade"] = "F"
102+
elif scorecard["angles_passing"] == scorecard["angles_total"]:
103+
scorecard["overall_grade"] = "A"
104+
elif scorecard["angles_passing"] >= scorecard["angles_total"] - 2:
105+
scorecard["overall_grade"] = "B"
106+
else:
107+
scorecard["overall_grade"] = "C"
108+
109+
out_dir = repo / "scorecard"
110+
out_dir.mkdir(exist_ok=True)
111+
(out_dir / f"{repo.name}.json").write_text(json.dumps(scorecard, indent=2))
112+
113+
print("## Release Audit (8 angles)")
114+
print()
115+
print(f"**Overall grade: {scorecard['overall_grade']}** ({scorecard['angles_passing']}/{scorecard['angles_total']} angles passing, {scorecard['blockers']} blocker(s))")
116+
print()
117+
print("| Angle | Grade | Detail |")
118+
print("|-------|-------|--------|")
119+
for a in scorecard["angles"]:
120+
print(f"| {a['angle']} | {a['grade']} | {a['detail']} |")
121+
122+
if scorecard["blockers"] > 0:
123+
print(f"\n::error::{scorecard['blockers']} release-blocker angle(s) — see audit output above")
124+
sys.exit(1)
125+
PY

.gitignore

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
# Byte-compiled / optimized / compiled files
2+
__pycache__/
3+
*.py[cod]
4+
*$py.class
5+
6+
# C extensions
7+
*.so
8+
9+
# Distribution / packaging
10+
.Python
11+
build/
12+
develop-eggs/
13+
dist/
14+
downloads/
15+
eggs/
16+
.eggs/
17+
lib/
18+
lib64/
19+
parts/
20+
sdist/
21+
var/
22+
wheels/
23+
*.egg-info/
24+
*.egg
25+
26+
# PyInstaller
27+
*.manifest
28+
*.spec
29+
30+
# Installer logs
31+
pip-log.txt
32+
pip-delete-this-directory.txt
33+
34+
# Unit test / coverage
35+
htmlcov/
36+
.tox/
37+
.nox/
38+
.coverage
39+
.coverage.*
40+
.cache
41+
nosetests.xml
42+
coverage.xml
43+
*.cover
44+
*.py,cover
45+
.hypothesis/
46+
.pytest_cache/
47+
48+
# Translations
49+
*.mo
50+
*.pot
51+
52+
# Environments
53+
.env
54+
.venv
55+
env/
56+
venv/
57+
ENV/
58+
59+
# IDE
60+
.vscode/
61+
.idea/
62+
*.swp
63+
*.swo
64+
*~
65+
66+
# OS
67+
.DS_Store
68+
Thumbs.db
69+
70+
# Project specific
71+
research/
72+
fixtures/generated/
73+
74+
# Added by release-prep
75+
node_modules

AGENTS.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
DevForge CLI meta-package that installs all 11 developer tools in one command. Provides a unified `devforge` CLI entry point delegating to sub-tools: api-contract-guardian, json2sql, deploydiff, configdrift, apighost, apiauth, envault, schemaforge, click-to-mcp, and deadcode.
55

66
## Build & Test Commands
7-
- Install: `pip install -e ".[all]"` or `pip install "git+https://github.com/Coding-Dev-Tools/devforge-cli.git[all]"` (NOTE: `devforge-tools` is not on public PyPI — use the `git+` form)
7+
- Install: `pip install -e .[all]` or `pip install devforge-tools`
8+
- Install all tools: `pip install devforge-tools[all]`
89
- Test: `pytest tests/` (or `python -m pytest tests/ -v --tb=short`)
910
- Lint: `ruff check .`
1011
- Build: `pip install build twine && python -m build && twine check dist/*`

CHANGELOG.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# Changelog
2+
3+
All notable changes to Revenue Holdings CLI will be documented in this file.
4+
5+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7+
8+
## [0.4.0] - 2026-06-30
9+
10+
### Changed
11+
- Renamed PyPI package from `devforge` to `devforge-tools` (name `devforge` was squatted on PyPI)
12+
- Updated all URLs, badges, and references to point to `devforge-tools`
13+
14+
## [0.3.0] - 2026-06-30
15+
16+
### Added
17+
- Python 3.13 to CI test matrix
18+
- Formatting check step in CI workflow
19+
- `format-check` Makefile target
20+
21+
### Changed
22+
- Makefile lint/format targets scoped to `src/ tests/` instead of entire repo
23+
24+
## [0.2.0] - 2026-05-17
25+
26+
### Added
27+
- Support for all 10 CLI tools (previously only 4)
28+
- New tool subcommands: ghost, auth, envault, schema, mcp, deadcode
29+
- CI/CD workflows for testing and PyPI publishing
30+
- Comprehensive .gitignore
31+
32+
### Changed
33+
- Updated tool registry to include all 10 tools
34+
- Made revenueholdings-license an optional dependency
35+
- Standardized pyproject.toml across all repos
36+
37+
## [0.1.0] - 2026-05-14
38+
39+
### Added
40+
- Initial release with 4 tools: guard, sql, deploy, drift
41+
- Unified `rh` CLI entry point
42+
- Tool dispatching via subprocess

0 commit comments

Comments
 (0)