devcoach 0.1.0__tar.gz → 0.3.6__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.
- devcoach-0.3.6/.github/scripts/fixtures/devcoach-backup.zip +0 -0
- devcoach-0.3.6/.github/scripts/take_screenshots.py +110 -0
- devcoach-0.3.6/.github/workflows/ci.yml +253 -0
- {devcoach-0.1.0 → devcoach-0.3.6}/.github/workflows/ruff-autofix.yml +2 -2
- devcoach-0.3.6/.github/workflows/update-screenshots.yml +39 -0
- {devcoach-0.1.0 → devcoach-0.3.6}/CLAUDE.md +3 -1
- {devcoach-0.1.0 → devcoach-0.3.6}/PKG-INFO +14 -3
- {devcoach-0.1.0 → devcoach-0.3.6}/README.md +13 -2
- devcoach-0.3.6/docs/favicon.svg +1 -0
- devcoach-0.3.6/docs/index.md +55 -0
- devcoach-0.3.6/docs/screenshots/knowledge-map-dark.png +0 -0
- devcoach-0.3.6/docs/screenshots/knowledge-map-light.png +0 -0
- devcoach-0.3.6/docs/screenshots/lessons-dark.png +0 -0
- devcoach-0.3.6/docs/screenshots/lessons-light.png +0 -0
- devcoach-0.3.6/docs/screenshots/settings-dark.png +0 -0
- devcoach-0.3.6/docs/screenshots/settings-light.png +0 -0
- devcoach-0.3.6/mkdocs.yml +54 -0
- {devcoach-0.1.0 → devcoach-0.3.6}/pyproject.toml +13 -1
- devcoach-0.3.6/sonar-project.properties +11 -0
- {devcoach-0.1.0 → devcoach-0.3.6}/src/devcoach/core/db.py +17 -2
- {devcoach-0.1.0 → devcoach-0.3.6}/src/devcoach/core/models.py +1 -0
- {devcoach-0.1.0 → devcoach-0.3.6}/src/devcoach/web/app.py +31 -4
- {devcoach-0.1.0 → devcoach-0.3.6}/src/devcoach/web/templates/base.html +16 -5
- {devcoach-0.1.0 → devcoach-0.3.6}/src/devcoach/web/templates/lessons.html +30 -28
- {devcoach-0.1.0 → devcoach-0.3.6}/src/devcoach/web/templates/profile.html +8 -8
- {devcoach-0.1.0 → devcoach-0.3.6}/src/devcoach/web/templates/settings.html +26 -4
- devcoach-0.3.6/tests/test_cli_commands.py +514 -0
- devcoach-0.3.6/tests/test_coach.py +159 -0
- devcoach-0.3.6/tests/test_db_extra.py +413 -0
- devcoach-0.3.6/tests/test_detect.py +111 -0
- devcoach-0.3.6/tests/test_git.py +140 -0
- devcoach-0.3.6/tests/test_mcp_server.py +532 -0
- devcoach-0.3.6/tests/test_prompts.py +126 -0
- devcoach-0.3.6/tests/test_web_extra.py +388 -0
- {devcoach-0.1.0 → devcoach-0.3.6}/uv.lock +509 -1
- devcoach-0.1.0/.github/workflows/ci.yml +0 -110
- {devcoach-0.1.0 → devcoach-0.3.6}/.github/dependabot.yml +0 -0
- {devcoach-0.1.0 → devcoach-0.3.6}/.gitignore +0 -0
- {devcoach-0.1.0 → devcoach-0.3.6}/LICENSE +0 -0
- {devcoach-0.1.0 → devcoach-0.3.6}/NOTICE +0 -0
- {devcoach-0.1.0 → devcoach-0.3.6}/SKILL.md +0 -0
- {devcoach-0.1.0 → devcoach-0.3.6}/docs/PLAN.md +0 -0
- {devcoach-0.1.0 → devcoach-0.3.6}/docs/cli.md +0 -0
- {devcoach-0.1.0 → devcoach-0.3.6}/docs/configuration.md +0 -0
- {devcoach-0.1.0 → devcoach-0.3.6}/docs/getting-started.md +0 -0
- {devcoach-0.1.0 → devcoach-0.3.6}/docs/mcp-server.md +0 -0
- {devcoach-0.1.0 → devcoach-0.3.6}/docs/web-ui.md +0 -0
- {devcoach-0.1.0 → devcoach-0.3.6}/src/devcoach/SKILL.md +0 -0
- {devcoach-0.1.0 → devcoach-0.3.6}/src/devcoach/__init__.py +0 -0
- {devcoach-0.1.0 → devcoach-0.3.6}/src/devcoach/cli/__init__.py +0 -0
- {devcoach-0.1.0 → devcoach-0.3.6}/src/devcoach/cli/commands.py +0 -0
- {devcoach-0.1.0 → devcoach-0.3.6}/src/devcoach/core/__init__.py +0 -0
- {devcoach-0.1.0 → devcoach-0.3.6}/src/devcoach/core/coach.py +0 -0
- {devcoach-0.1.0 → devcoach-0.3.6}/src/devcoach/core/detect.py +0 -0
- {devcoach-0.1.0 → devcoach-0.3.6}/src/devcoach/core/git.py +0 -0
- {devcoach-0.1.0 → devcoach-0.3.6}/src/devcoach/core/prompts.py +0 -0
- {devcoach-0.1.0 → devcoach-0.3.6}/src/devcoach/mcp/__init__.py +0 -0
- {devcoach-0.1.0 → devcoach-0.3.6}/src/devcoach/mcp/server.py +0 -0
- {devcoach-0.1.0 → devcoach-0.3.6}/src/devcoach/web/__init__.py +0 -0
- {devcoach-0.1.0 → devcoach-0.3.6}/src/devcoach/web/static/favicon.svg +0 -0
- {devcoach-0.1.0 → devcoach-0.3.6}/src/devcoach/web/static/relative-time.js +0 -0
- {devcoach-0.1.0 → devcoach-0.3.6}/src/devcoach/web/static/style.css +0 -0
- {devcoach-0.1.0 → devcoach-0.3.6}/src/devcoach/web/static/vendor/alpinejs.min.js +0 -0
- {devcoach-0.1.0 → devcoach-0.3.6}/src/devcoach/web/static/vendor/flatpickr-dark.min.css +0 -0
- {devcoach-0.1.0 → devcoach-0.3.6}/src/devcoach/web/static/vendor/flatpickr.min.css +0 -0
- {devcoach-0.1.0 → devcoach-0.3.6}/src/devcoach/web/static/vendor/flatpickr.min.js +0 -0
- {devcoach-0.1.0 → devcoach-0.3.6}/src/devcoach/web/static/vendor/highlight.min.js +0 -0
- {devcoach-0.1.0 → devcoach-0.3.6}/src/devcoach/web/static/vendor/hljs-dark.min.css +0 -0
- {devcoach-0.1.0 → devcoach-0.3.6}/src/devcoach/web/static/vendor/hljs-light.min.css +0 -0
- {devcoach-0.1.0 → devcoach-0.3.6}/src/devcoach/web/static/vendor/htmx.min.js +0 -0
- {devcoach-0.1.0 → devcoach-0.3.6}/src/devcoach/web/static/vendor/icons/bitbucket.svg +0 -0
- {devcoach-0.1.0 → devcoach-0.3.6}/src/devcoach/web/static/vendor/icons/github.svg +0 -0
- {devcoach-0.1.0 → devcoach-0.3.6}/src/devcoach/web/static/vendor/icons/gitlab.svg +0 -0
- {devcoach-0.1.0 → devcoach-0.3.6}/src/devcoach/web/static/vendor/icons/vscode.svg +0 -0
- {devcoach-0.1.0 → devcoach-0.3.6}/src/devcoach/web/static/vendor/marked.min.js +0 -0
- {devcoach-0.1.0 → devcoach-0.3.6}/src/devcoach/web/static/vendor/tailwind.js +0 -0
- {devcoach-0.1.0 → devcoach-0.3.6}/src/devcoach/web/templates/lesson_detail.html +0 -0
- {devcoach-0.1.0 → devcoach-0.3.6}/tests/__init__.py +0 -0
- {devcoach-0.1.0 → devcoach-0.3.6}/tests/conftest.py +0 -0
- {devcoach-0.1.0 → devcoach-0.3.6}/tests/test_cli.py +0 -0
- {devcoach-0.1.0 → devcoach-0.3.6}/tests/test_web.py +0 -0
|
Binary file
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"""Take devcoach UI screenshots for documentation.
|
|
2
|
+
|
|
3
|
+
Flow:
|
|
4
|
+
1. Restore DB from .github/scripts/fixtures/devcoach-backup.zip via `devcoach restore`
|
|
5
|
+
2. Start `devcoach ui` on a fixed port
|
|
6
|
+
3. Capture light + dark screenshots of each page
|
|
7
|
+
4. Stop the server
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import signal
|
|
11
|
+
import subprocess
|
|
12
|
+
import sys
|
|
13
|
+
import time
|
|
14
|
+
import urllib.request
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
SCREENSHOTS_DIR = Path("docs/screenshots")
|
|
18
|
+
FIXTURES_DIR = Path(".github/scripts/fixtures")
|
|
19
|
+
BACKUP_ZIP = FIXTURES_DIR / "devcoach-backup.zip"
|
|
20
|
+
PORT = 7862
|
|
21
|
+
BASE_URL = f"http://localhost:{PORT}"
|
|
22
|
+
VIEWPORT = {"width": 1440, "height": 900}
|
|
23
|
+
|
|
24
|
+
PAGES = [
|
|
25
|
+
("knowledge-map", "/"),
|
|
26
|
+
("lessons", "/lessons"),
|
|
27
|
+
("settings", "/settings"),
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
def restore_db() -> None:
|
|
31
|
+
result = subprocess.run(
|
|
32
|
+
["devcoach", "restore", str(BACKUP_ZIP)],
|
|
33
|
+
check=True,
|
|
34
|
+
capture_output=True,
|
|
35
|
+
text=True,
|
|
36
|
+
)
|
|
37
|
+
print(result.stdout.strip())
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def wait_for_server(url: str, timeout: int = 30) -> None:
|
|
41
|
+
for _ in range(timeout):
|
|
42
|
+
try:
|
|
43
|
+
urllib.request.urlopen(url, timeout=1)
|
|
44
|
+
return
|
|
45
|
+
except Exception:
|
|
46
|
+
time.sleep(1)
|
|
47
|
+
raise TimeoutError(f"Server at {url} did not start within {timeout}s")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def take_screenshots(server_proc: subprocess.Popen) -> None:
|
|
51
|
+
from playwright.sync_api import sync_playwright
|
|
52
|
+
|
|
53
|
+
SCREENSHOTS_DIR.mkdir(parents=True, exist_ok=True)
|
|
54
|
+
|
|
55
|
+
with sync_playwright() as pw:
|
|
56
|
+
browser = pw.chromium.launch()
|
|
57
|
+
|
|
58
|
+
ctx = browser.new_context(viewport=VIEWPORT, color_scheme="light")
|
|
59
|
+
page = ctx.new_page()
|
|
60
|
+
for name, path in PAGES:
|
|
61
|
+
page.goto(f"{BASE_URL}{path}")
|
|
62
|
+
page.wait_for_load_state("networkidle")
|
|
63
|
+
out = SCREENSHOTS_DIR / f"{name}-light.png"
|
|
64
|
+
page.screenshot(path=str(out))
|
|
65
|
+
print(f" saved {out}")
|
|
66
|
+
ctx.close()
|
|
67
|
+
|
|
68
|
+
ctx = browser.new_context(viewport=VIEWPORT, color_scheme="dark")
|
|
69
|
+
page = ctx.new_page()
|
|
70
|
+
for name, path in PAGES:
|
|
71
|
+
page.goto(f"{BASE_URL}{path}")
|
|
72
|
+
page.wait_for_load_state("networkidle")
|
|
73
|
+
out = SCREENSHOTS_DIR / f"{name}-dark.png"
|
|
74
|
+
page.screenshot(path=str(out))
|
|
75
|
+
print(f" saved {out}")
|
|
76
|
+
ctx.close()
|
|
77
|
+
|
|
78
|
+
browser.close()
|
|
79
|
+
|
|
80
|
+
server_proc.send_signal(signal.SIGTERM)
|
|
81
|
+
server_proc.wait(timeout=10)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def main() -> None:
|
|
85
|
+
if not BACKUP_ZIP.exists():
|
|
86
|
+
sys.exit(f"Backup not found: {BACKUP_ZIP}")
|
|
87
|
+
|
|
88
|
+
print(f"Restoring DB from {BACKUP_ZIP}…")
|
|
89
|
+
restore_db()
|
|
90
|
+
|
|
91
|
+
print(f"Starting devcoach UI on port {PORT}…")
|
|
92
|
+
proc = subprocess.Popen(
|
|
93
|
+
["devcoach", "ui", "--port", str(PORT)],
|
|
94
|
+
stdout=subprocess.DEVNULL,
|
|
95
|
+
stderr=subprocess.DEVNULL,
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
try:
|
|
99
|
+
wait_for_server(BASE_URL)
|
|
100
|
+
print("Taking screenshots…")
|
|
101
|
+
take_screenshots(proc)
|
|
102
|
+
except Exception:
|
|
103
|
+
proc.terminate()
|
|
104
|
+
raise
|
|
105
|
+
|
|
106
|
+
print("Done.")
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
if __name__ == "__main__":
|
|
110
|
+
main()
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
run-name: >-
|
|
4
|
+
${{ github.event_name == 'workflow_dispatch'
|
|
5
|
+
&& format('CI — New release ({0})', inputs.version != '' && inputs.version || inputs.bump)
|
|
6
|
+
|| (startsWith(github.ref, 'refs/tags/v')
|
|
7
|
+
&& format('CI — New release ({0})', github.ref_name)
|
|
8
|
+
|| (github.event_name == 'pull_request'
|
|
9
|
+
&& format('CI - PR {0}', github.event.pull_request.title)
|
|
10
|
+
|| format('CI — {0}', github.event.head_commit.message))) }}
|
|
11
|
+
|
|
12
|
+
# Covers three scenarios:
|
|
13
|
+
#
|
|
14
|
+
# 1. Push to main / pull request → lint + test + build + sonar + pages
|
|
15
|
+
# 2. Tag push (v*) → lint + test + build + publish + GitHub Release
|
|
16
|
+
# 3. workflow_dispatch (Release) → bump version → tag → lint + test + build + publish + GitHub Release
|
|
17
|
+
#
|
|
18
|
+
# The bump job only runs on workflow_dispatch. Lint/test/build always run.
|
|
19
|
+
# Publish and GitHub Release run whenever a tag is present.
|
|
20
|
+
|
|
21
|
+
on:
|
|
22
|
+
push:
|
|
23
|
+
branches: [main]
|
|
24
|
+
tags: ["v*"]
|
|
25
|
+
pull_request:
|
|
26
|
+
workflow_dispatch:
|
|
27
|
+
inputs:
|
|
28
|
+
bump:
|
|
29
|
+
description: "Version bump type"
|
|
30
|
+
required: false
|
|
31
|
+
default: patch
|
|
32
|
+
type: choice
|
|
33
|
+
options: [patch, minor, major]
|
|
34
|
+
version:
|
|
35
|
+
description: "Exact version (overrides bump, e.g. 1.0.0)"
|
|
36
|
+
required: false
|
|
37
|
+
default: ""
|
|
38
|
+
|
|
39
|
+
concurrency:
|
|
40
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
41
|
+
cancel-in-progress: true
|
|
42
|
+
|
|
43
|
+
jobs:
|
|
44
|
+
# ── Bump ────────────────────────────────────────────────────────────────
|
|
45
|
+
# Only runs on workflow_dispatch. Bumps pyproject.toml, commits, tags, pushes.
|
|
46
|
+
bump:
|
|
47
|
+
name: Bump version and tag
|
|
48
|
+
if: github.event_name == 'workflow_dispatch'
|
|
49
|
+
runs-on: ubuntu-latest
|
|
50
|
+
permissions:
|
|
51
|
+
contents: write
|
|
52
|
+
outputs:
|
|
53
|
+
version: ${{ steps.ver.outputs.version }}
|
|
54
|
+
tag: ${{ steps.ver.outputs.tag }}
|
|
55
|
+
steps:
|
|
56
|
+
- uses: actions/checkout@v6
|
|
57
|
+
with:
|
|
58
|
+
fetch-depth: 0
|
|
59
|
+
|
|
60
|
+
- name: Configure git
|
|
61
|
+
run: |
|
|
62
|
+
git config user.name "github-actions[bot]"
|
|
63
|
+
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
64
|
+
|
|
65
|
+
- name: Compute next version
|
|
66
|
+
id: ver
|
|
67
|
+
run: |
|
|
68
|
+
if [ -n "${{ inputs.version }}" ]; then
|
|
69
|
+
NEXT="${{ inputs.version }}"
|
|
70
|
+
else
|
|
71
|
+
CURRENT=$(grep '^version' pyproject.toml | head -1 | sed 's/.*"\(.*\)"/\1/')
|
|
72
|
+
IFS='.' read -r MAJOR MINOR PATCH <<< "$CURRENT"
|
|
73
|
+
case "${{ inputs.bump }}" in
|
|
74
|
+
major) NEXT="$((MAJOR+1)).0.0" ;;
|
|
75
|
+
minor) NEXT="${MAJOR}.$((MINOR+1)).0" ;;
|
|
76
|
+
patch) NEXT="${MAJOR}.${MINOR}.$((PATCH+1))" ;;
|
|
77
|
+
esac
|
|
78
|
+
fi
|
|
79
|
+
echo "version=$NEXT" >> "$GITHUB_OUTPUT"
|
|
80
|
+
echo "tag=v$NEXT" >> "$GITHUB_OUTPUT"
|
|
81
|
+
|
|
82
|
+
- name: Bump version in pyproject.toml and sonar-project.properties
|
|
83
|
+
run: |
|
|
84
|
+
sed -i 's/^version = ".*"/version = "${{ steps.ver.outputs.version }}"/' pyproject.toml
|
|
85
|
+
sed -i 's/^sonar.projectVersion=.*/sonar.projectVersion=${{ steps.ver.outputs.version }}/' sonar-project.properties
|
|
86
|
+
|
|
87
|
+
- name: Commit and tag
|
|
88
|
+
run: |
|
|
89
|
+
git add pyproject.toml sonar-project.properties
|
|
90
|
+
git commit -m "chore: bump version to ${{ steps.ver.outputs.version }}"
|
|
91
|
+
git tag "${{ steps.ver.outputs.tag }}"
|
|
92
|
+
git push --atomic origin main "${{ steps.ver.outputs.tag }}"
|
|
93
|
+
|
|
94
|
+
# ── Lint ────────────────────────────────────────────────────────────────
|
|
95
|
+
lint:
|
|
96
|
+
name: Lint (ruff)
|
|
97
|
+
needs: [bump]
|
|
98
|
+
if: always() && (needs.bump.result == 'success' || needs.bump.result == 'skipped')
|
|
99
|
+
runs-on: ubuntu-latest
|
|
100
|
+
steps:
|
|
101
|
+
- uses: actions/checkout@v6
|
|
102
|
+
with:
|
|
103
|
+
ref: ${{ needs.bump.outputs.tag || github.ref }}
|
|
104
|
+
- uses: astral-sh/setup-uv@v7
|
|
105
|
+
with:
|
|
106
|
+
python-version: "3.12"
|
|
107
|
+
- run: uv sync --group dev
|
|
108
|
+
- run: uv run ruff check src/ tests/
|
|
109
|
+
- run: uv run ruff format --check src/ tests/
|
|
110
|
+
|
|
111
|
+
# ── Tests ───────────────────────────────────────────────────────────────
|
|
112
|
+
test:
|
|
113
|
+
name: Test (Python ${{ matrix.python-version }})
|
|
114
|
+
needs: [bump]
|
|
115
|
+
if: always() && (needs.bump.result == 'success' || needs.bump.result == 'skipped')
|
|
116
|
+
runs-on: ubuntu-latest
|
|
117
|
+
strategy:
|
|
118
|
+
fail-fast: false
|
|
119
|
+
matrix:
|
|
120
|
+
python-version: ["3.11", "3.12", "3.13"]
|
|
121
|
+
steps:
|
|
122
|
+
- uses: actions/checkout@v6
|
|
123
|
+
with:
|
|
124
|
+
ref: ${{ needs.bump.outputs.tag || github.ref }}
|
|
125
|
+
- uses: astral-sh/setup-uv@v7
|
|
126
|
+
with:
|
|
127
|
+
python-version: ${{ matrix.python-version }}
|
|
128
|
+
- run: uv sync --group dev
|
|
129
|
+
- name: Run tests
|
|
130
|
+
run: uv run pytest tests/ -v --tb=short
|
|
131
|
+
|
|
132
|
+
# ── Build ───────────────────────────────────────────────────────────────
|
|
133
|
+
build:
|
|
134
|
+
name: Build distribution
|
|
135
|
+
needs: [bump, lint, test]
|
|
136
|
+
if: always() && (needs.bump.result == 'success' || needs.bump.result == 'skipped') && needs.lint.result == 'success' && needs.test.result == 'success'
|
|
137
|
+
runs-on: ubuntu-latest
|
|
138
|
+
steps:
|
|
139
|
+
- uses: actions/checkout@v6
|
|
140
|
+
with:
|
|
141
|
+
ref: ${{ needs.bump.outputs.tag || github.ref }}
|
|
142
|
+
- uses: astral-sh/setup-uv@v7
|
|
143
|
+
with:
|
|
144
|
+
python-version: "3.12"
|
|
145
|
+
- run: uv build
|
|
146
|
+
- uses: actions/upload-artifact@v7
|
|
147
|
+
with:
|
|
148
|
+
name: dist
|
|
149
|
+
path: dist/
|
|
150
|
+
retention-days: 7
|
|
151
|
+
|
|
152
|
+
# ── SonarCloud scan + quality gate ─────────────────────────────────────
|
|
153
|
+
# CI-based analysis: feeds coverage.xml to SonarCloud and blocks PR merge
|
|
154
|
+
# if the quality gate fails. Requires Automatic Analysis to be DISABLED on
|
|
155
|
+
# sonarcloud.io (Administration → Analysis Method).
|
|
156
|
+
sonar:
|
|
157
|
+
name: SonarCloud scan
|
|
158
|
+
needs: [bump, test]
|
|
159
|
+
if: always() && (needs.bump.result == 'success' || needs.bump.result == 'skipped') && needs.test.result == 'success'
|
|
160
|
+
runs-on: ubuntu-latest
|
|
161
|
+
steps:
|
|
162
|
+
- uses: actions/checkout@v6
|
|
163
|
+
with:
|
|
164
|
+
ref: ${{ needs.bump.outputs.tag || github.ref }}
|
|
165
|
+
fetch-depth: 0
|
|
166
|
+
- uses: astral-sh/setup-uv@v7
|
|
167
|
+
with:
|
|
168
|
+
python-version: "3.12"
|
|
169
|
+
- run: uv sync --group dev
|
|
170
|
+
- name: Run tests with coverage
|
|
171
|
+
run: uv run pytest tests/ -v --tb=short --cov=src/devcoach --cov-report=xml
|
|
172
|
+
- uses: sonarsource/sonarqube-scan-action@v6
|
|
173
|
+
env:
|
|
174
|
+
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
|
175
|
+
- uses: sonarsource/sonarqube-quality-gate-action@v1
|
|
176
|
+
if: github.event_name == 'pull_request'
|
|
177
|
+
timeout-minutes: 5
|
|
178
|
+
env:
|
|
179
|
+
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
|
180
|
+
|
|
181
|
+
# ── GitHub Pages ────────────────────────────────────────────────────────
|
|
182
|
+
# Builds MkDocs site and deploys to GitHub Pages on every push to main.
|
|
183
|
+
pages:
|
|
184
|
+
name: Deploy docs to GitHub Pages
|
|
185
|
+
needs: [bump, build]
|
|
186
|
+
if: always() && needs.build.result == 'success' && github.ref == 'refs/heads/main' && github.event_name == 'push'
|
|
187
|
+
runs-on: ubuntu-latest
|
|
188
|
+
permissions:
|
|
189
|
+
pages: write
|
|
190
|
+
id-token: write
|
|
191
|
+
environment:
|
|
192
|
+
name: github-pages
|
|
193
|
+
url: ${{ steps.deploy.outputs.page_url }}
|
|
194
|
+
steps:
|
|
195
|
+
- uses: actions/checkout@v6
|
|
196
|
+
- uses: astral-sh/setup-uv@v7
|
|
197
|
+
with:
|
|
198
|
+
python-version: "3.12"
|
|
199
|
+
- run: uv sync --group docs
|
|
200
|
+
- run: uv run mkdocs build
|
|
201
|
+
- uses: actions/upload-pages-artifact@v3
|
|
202
|
+
with:
|
|
203
|
+
path: site/
|
|
204
|
+
- name: Deploy to GitHub Pages
|
|
205
|
+
id: deploy
|
|
206
|
+
uses: actions/deploy-pages@v4
|
|
207
|
+
|
|
208
|
+
# ── Publish ─────────────────────────────────────────────────────────────
|
|
209
|
+
# Runs when a tag is present: either created by the bump job (workflow_dispatch)
|
|
210
|
+
# or pushed directly (git push origin v1.2.3).
|
|
211
|
+
# Uses PyPI Trusted Publishing (OIDC) — no API tokens needed.
|
|
212
|
+
#
|
|
213
|
+
# One-time setup on PyPI: project=devcoach, owner=UltimaPhoenix,
|
|
214
|
+
# repo=dev-coach, workflow=ci.yml, environment=pypi
|
|
215
|
+
publish:
|
|
216
|
+
name: Publish to PyPI
|
|
217
|
+
needs: [bump, build]
|
|
218
|
+
if: always() && needs.build.result == 'success' && (needs.bump.result == 'success' || startsWith(github.ref, 'refs/tags/v'))
|
|
219
|
+
runs-on: ubuntu-latest
|
|
220
|
+
environment:
|
|
221
|
+
name: pypi
|
|
222
|
+
url: https://pypi.org/p/devcoach
|
|
223
|
+
permissions:
|
|
224
|
+
id-token: write
|
|
225
|
+
steps:
|
|
226
|
+
- uses: actions/download-artifact@v8
|
|
227
|
+
with:
|
|
228
|
+
name: dist
|
|
229
|
+
path: dist/
|
|
230
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
231
|
+
|
|
232
|
+
# ── GitHub Release ──────────────────────────────────────────────────────
|
|
233
|
+
github-release:
|
|
234
|
+
name: Create GitHub Release
|
|
235
|
+
needs: [bump, publish]
|
|
236
|
+
if: always() && needs.publish.result == 'success' && (needs.bump.result == 'success' || startsWith(github.ref, 'refs/tags/v'))
|
|
237
|
+
runs-on: ubuntu-latest
|
|
238
|
+
permissions:
|
|
239
|
+
contents: write
|
|
240
|
+
steps:
|
|
241
|
+
- uses: actions/checkout@v6
|
|
242
|
+
with:
|
|
243
|
+
ref: ${{ needs.bump.outputs.tag || github.ref }}
|
|
244
|
+
- uses: actions/download-artifact@v8
|
|
245
|
+
with:
|
|
246
|
+
name: dist
|
|
247
|
+
path: dist/
|
|
248
|
+
- uses: softprops/action-gh-release@v3
|
|
249
|
+
with:
|
|
250
|
+
tag_name: ${{ needs.bump.outputs.tag || github.ref_name }}
|
|
251
|
+
files: dist/*
|
|
252
|
+
generate_release_notes: true
|
|
253
|
+
fail_on_unmatched_files: true
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
name: Update documentation screenshots (GitHub Pages)
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
|
|
6
|
+
jobs:
|
|
7
|
+
screenshots:
|
|
8
|
+
name: Take and commit screenshots
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
permissions:
|
|
11
|
+
contents: write
|
|
12
|
+
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v6
|
|
15
|
+
|
|
16
|
+
- uses: astral-sh/setup-uv@v7
|
|
17
|
+
with:
|
|
18
|
+
python-version: "3.12"
|
|
19
|
+
|
|
20
|
+
- name: Install devcoach
|
|
21
|
+
run: uv sync
|
|
22
|
+
|
|
23
|
+
- name: Install Playwright + Chromium
|
|
24
|
+
run: |
|
|
25
|
+
uv pip install playwright
|
|
26
|
+
uv run playwright install chromium --with-deps
|
|
27
|
+
|
|
28
|
+
- name: Restore DB and take screenshots
|
|
29
|
+
run: uv run python .github/scripts/take_screenshots.py
|
|
30
|
+
|
|
31
|
+
- name: Commit screenshots
|
|
32
|
+
run: |
|
|
33
|
+
git config user.name "github-actions[bot]"
|
|
34
|
+
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
35
|
+
git add docs/screenshots/
|
|
36
|
+
git diff --staged --quiet && echo "No changes." || (
|
|
37
|
+
git commit -m "docs: update screenshots [skip ci]" &&
|
|
38
|
+
git push
|
|
39
|
+
)
|
|
@@ -250,7 +250,9 @@ npx @modelcontextprotocol/inspector devcoach
|
|
|
250
250
|
|
|
251
251
|
## Development conventions
|
|
252
252
|
|
|
253
|
-
-
|
|
253
|
+
- Follow **Uncle Bob's Clean Code** principles
|
|
254
|
+
- Follow **PEP** standards
|
|
255
|
+
- Linting and formatting enforced by **ruff** — run `uv run ruff check src/ tests/` and `uv run ruff format src/ tests/` before committing
|
|
254
256
|
- No external dependencies beyond `fastmcp` and `pydantic`
|
|
255
257
|
- `db.py` exposes only pure functions — no business logic
|
|
256
258
|
- `coach.py` never imports from `server.py` (one-way dependency)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: devcoach
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.6
|
|
4
4
|
Summary: A local MCP server that acts as a progressive technical coach for Claude Code and Claude Desktop
|
|
5
5
|
Project-URL: Homepage, https://github.com/UltimaPhoenix/dev-coach
|
|
6
6
|
Project-URL: Repository, https://github.com/UltimaPhoenix/dev-coach
|
|
@@ -232,9 +232,12 @@ Description-Content-Type: text/markdown
|
|
|
232
232
|
|
|
233
233
|
# devcoach
|
|
234
234
|
|
|
235
|
-
[](https://pypi.org/project/devcoach/)
|
|
236
|
+
[](https://pypi.org/project/devcoach/)
|
|
237
237
|
[](https://github.com/UltimaPhoenix/dev-coach/actions/workflows/ci.yml)
|
|
238
|
+
[](https://sonarcloud.io/summary/new_code?id=UltimaPhoenix_dev-coach)
|
|
239
|
+
[](https://sonarcloud.io/summary/new_code?id=UltimaPhoenix_dev-coach)
|
|
240
|
+
[](https://ultimaphoenix.github.io/dev-coach/)
|
|
238
241
|
[](LICENSE)
|
|
239
242
|
|
|
240
243
|
**Progressive technical coaching, directly in Claude.** After every task you complete with Claude Code or Claude Desktop, devcoach delivers a short, targeted lesson based on what you already know — no generic tutorials, no repeated topics.
|
|
@@ -254,6 +257,14 @@ Everything runs **locally**. No data leaves your machine. One SQLite file at `~/
|
|
|
254
257
|
|
|
255
258
|
---
|
|
256
259
|
|
|
260
|
+
## Screenshots
|
|
261
|
+
|
|
262
|
+
| Knowledge map | Lesson history | Settings |
|
|
263
|
+
|:---:|:---:|:---:|
|
|
264
|
+
|  |  |  |
|
|
265
|
+
|
|
266
|
+
---
|
|
267
|
+
|
|
257
268
|
## Installation
|
|
258
269
|
|
|
259
270
|
### Recommended — no permanent install needed
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
# devcoach
|
|
2
2
|
|
|
3
|
-
[](https://pypi.org/project/devcoach/)
|
|
4
|
+
[](https://pypi.org/project/devcoach/)
|
|
5
5
|
[](https://github.com/UltimaPhoenix/dev-coach/actions/workflows/ci.yml)
|
|
6
|
+
[](https://sonarcloud.io/summary/new_code?id=UltimaPhoenix_dev-coach)
|
|
7
|
+
[](https://sonarcloud.io/summary/new_code?id=UltimaPhoenix_dev-coach)
|
|
8
|
+
[](https://ultimaphoenix.github.io/dev-coach/)
|
|
6
9
|
[](LICENSE)
|
|
7
10
|
|
|
8
11
|
**Progressive technical coaching, directly in Claude.** After every task you complete with Claude Code or Claude Desktop, devcoach delivers a short, targeted lesson based on what you already know — no generic tutorials, no repeated topics.
|
|
@@ -22,6 +25,14 @@ Everything runs **locally**. No data leaves your machine. One SQLite file at `~/
|
|
|
22
25
|
|
|
23
26
|
---
|
|
24
27
|
|
|
28
|
+
## Screenshots
|
|
29
|
+
|
|
30
|
+
| Knowledge map | Lesson history | Settings |
|
|
31
|
+
|:---:|:---:|:---:|
|
|
32
|
+
|  |  |  |
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
25
36
|
## Installation
|
|
26
37
|
|
|
27
38
|
### Recommended — no permanent install needed
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
../src/devcoach/web/static/favicon.svg
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# devcoach
|
|
2
|
+
|
|
3
|
+
**Progressive technical coaching, directly in Claude.** After every task you complete with Claude Code or Claude Desktop, devcoach delivers a short, targeted lesson based on what you already know — no generic tutorials, no repeated topics.
|
|
4
|
+
|
|
5
|
+
Everything runs **locally**. No data leaves your machine. One SQLite file at `~/.devcoach/coaching.db`.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## How it works
|
|
10
|
+
|
|
11
|
+
| Step | What happens |
|
|
12
|
+
|------|-------------|
|
|
13
|
+
| You complete a task with Claude | Claude finishes the work as normal |
|
|
14
|
+
| devcoach checks your knowledge map | Finds a topic where you have room to grow, related to what you just did |
|
|
15
|
+
| A lesson appears at the end of the response | Calibrated to your level (junior / mid / senior), never repeated |
|
|
16
|
+
| You mark it know / don't know | Confidence scores update, shaping future lessons |
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Screenshots
|
|
21
|
+
|
|
22
|
+
### Knowledge map
|
|
23
|
+
|
|
24
|
+
=== "Dark"
|
|
25
|
+

|
|
26
|
+
|
|
27
|
+
=== "Light"
|
|
28
|
+

|
|
29
|
+
|
|
30
|
+
### Lesson history
|
|
31
|
+
|
|
32
|
+
=== "Dark"
|
|
33
|
+

|
|
34
|
+
|
|
35
|
+
=== "Light"
|
|
36
|
+

|
|
37
|
+
|
|
38
|
+
### Settings
|
|
39
|
+
|
|
40
|
+
=== "Dark"
|
|
41
|
+

|
|
42
|
+
|
|
43
|
+
=== "Light"
|
|
44
|
+

|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Quick install
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
uv tool install devcoach
|
|
52
|
+
devcoach install # registers with Claude Code / Claude Desktop
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Restart Claude and you're ready. See [Getting started](getting-started.md) for the full onboarding walkthrough.
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
site_name: devcoach
|
|
2
|
+
site_description: Progressive technical coaching, directly in Claude
|
|
3
|
+
site_url: https://ultimaphoenix.github.io/dev-coach/
|
|
4
|
+
repo_url: https://github.com/UltimaPhoenix/dev-coach
|
|
5
|
+
repo_name: UltimaPhoenix/dev-coach
|
|
6
|
+
edit_uri: edit/main/docs/
|
|
7
|
+
|
|
8
|
+
theme:
|
|
9
|
+
name: material
|
|
10
|
+
favicon: favicon.svg
|
|
11
|
+
logo: favicon.svg
|
|
12
|
+
palette:
|
|
13
|
+
- scheme: slate
|
|
14
|
+
primary: deep purple
|
|
15
|
+
accent: purple
|
|
16
|
+
toggle:
|
|
17
|
+
icon: material/brightness-4
|
|
18
|
+
name: Switch to light mode
|
|
19
|
+
- scheme: default
|
|
20
|
+
primary: deep purple
|
|
21
|
+
accent: purple
|
|
22
|
+
toggle:
|
|
23
|
+
icon: material/brightness-7
|
|
24
|
+
name: Switch to dark mode
|
|
25
|
+
features:
|
|
26
|
+
- navigation.top
|
|
27
|
+
- navigation.indexes
|
|
28
|
+
- search.highlight
|
|
29
|
+
- content.code.copy
|
|
30
|
+
- toc.integrate
|
|
31
|
+
icon:
|
|
32
|
+
repo: fontawesome/brands/github
|
|
33
|
+
|
|
34
|
+
nav:
|
|
35
|
+
- Home: index.md
|
|
36
|
+
- Getting started: getting-started.md
|
|
37
|
+
- CLI reference: cli.md
|
|
38
|
+
- MCP server: mcp-server.md
|
|
39
|
+
- Web UI: web-ui.md
|
|
40
|
+
- Configuration: configuration.md
|
|
41
|
+
|
|
42
|
+
plugins:
|
|
43
|
+
- search
|
|
44
|
+
|
|
45
|
+
markdown_extensions:
|
|
46
|
+
- admonition
|
|
47
|
+
- pymdownx.highlight:
|
|
48
|
+
anchor_linenums: true
|
|
49
|
+
- pymdownx.superfences
|
|
50
|
+
- pymdownx.tabbed:
|
|
51
|
+
alternate_style: true
|
|
52
|
+
- tables
|
|
53
|
+
- attr_list
|
|
54
|
+
- md_in_html
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "devcoach"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.3.6"
|
|
4
4
|
description = "A local MCP server that acts as a progressive technical coach for Claude Code and Claude Desktop"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.11"
|
|
@@ -49,9 +49,14 @@ packages = ["src/devcoach"]
|
|
|
49
49
|
[dependency-groups]
|
|
50
50
|
dev = [
|
|
51
51
|
"pytest>=8.0",
|
|
52
|
+
"pytest-cov>=5.0",
|
|
52
53
|
"httpx>=0.27",
|
|
53
54
|
"ruff>=0.4",
|
|
54
55
|
]
|
|
56
|
+
docs = [
|
|
57
|
+
"mkdocs>=1.6",
|
|
58
|
+
"mkdocs-material>=9.5",
|
|
59
|
+
]
|
|
55
60
|
|
|
56
61
|
[tool.ruff]
|
|
57
62
|
line-length = 100
|
|
@@ -63,3 +68,10 @@ ignore = ["E501"]
|
|
|
63
68
|
|
|
64
69
|
[tool.pytest.ini_options]
|
|
65
70
|
testpaths = ["tests"]
|
|
71
|
+
|
|
72
|
+
[tool.coverage.run]
|
|
73
|
+
source = ["src/devcoach"]
|
|
74
|
+
omit = ["tests/*"]
|
|
75
|
+
|
|
76
|
+
[tool.coverage.report]
|
|
77
|
+
omit = ["tests/*"]
|