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.
Files changed (81) hide show
  1. devcoach-0.3.6/.github/scripts/fixtures/devcoach-backup.zip +0 -0
  2. devcoach-0.3.6/.github/scripts/take_screenshots.py +110 -0
  3. devcoach-0.3.6/.github/workflows/ci.yml +253 -0
  4. {devcoach-0.1.0 → devcoach-0.3.6}/.github/workflows/ruff-autofix.yml +2 -2
  5. devcoach-0.3.6/.github/workflows/update-screenshots.yml +39 -0
  6. {devcoach-0.1.0 → devcoach-0.3.6}/CLAUDE.md +3 -1
  7. {devcoach-0.1.0 → devcoach-0.3.6}/PKG-INFO +14 -3
  8. {devcoach-0.1.0 → devcoach-0.3.6}/README.md +13 -2
  9. devcoach-0.3.6/docs/favicon.svg +1 -0
  10. devcoach-0.3.6/docs/index.md +55 -0
  11. devcoach-0.3.6/docs/screenshots/knowledge-map-dark.png +0 -0
  12. devcoach-0.3.6/docs/screenshots/knowledge-map-light.png +0 -0
  13. devcoach-0.3.6/docs/screenshots/lessons-dark.png +0 -0
  14. devcoach-0.3.6/docs/screenshots/lessons-light.png +0 -0
  15. devcoach-0.3.6/docs/screenshots/settings-dark.png +0 -0
  16. devcoach-0.3.6/docs/screenshots/settings-light.png +0 -0
  17. devcoach-0.3.6/mkdocs.yml +54 -0
  18. {devcoach-0.1.0 → devcoach-0.3.6}/pyproject.toml +13 -1
  19. devcoach-0.3.6/sonar-project.properties +11 -0
  20. {devcoach-0.1.0 → devcoach-0.3.6}/src/devcoach/core/db.py +17 -2
  21. {devcoach-0.1.0 → devcoach-0.3.6}/src/devcoach/core/models.py +1 -0
  22. {devcoach-0.1.0 → devcoach-0.3.6}/src/devcoach/web/app.py +31 -4
  23. {devcoach-0.1.0 → devcoach-0.3.6}/src/devcoach/web/templates/base.html +16 -5
  24. {devcoach-0.1.0 → devcoach-0.3.6}/src/devcoach/web/templates/lessons.html +30 -28
  25. {devcoach-0.1.0 → devcoach-0.3.6}/src/devcoach/web/templates/profile.html +8 -8
  26. {devcoach-0.1.0 → devcoach-0.3.6}/src/devcoach/web/templates/settings.html +26 -4
  27. devcoach-0.3.6/tests/test_cli_commands.py +514 -0
  28. devcoach-0.3.6/tests/test_coach.py +159 -0
  29. devcoach-0.3.6/tests/test_db_extra.py +413 -0
  30. devcoach-0.3.6/tests/test_detect.py +111 -0
  31. devcoach-0.3.6/tests/test_git.py +140 -0
  32. devcoach-0.3.6/tests/test_mcp_server.py +532 -0
  33. devcoach-0.3.6/tests/test_prompts.py +126 -0
  34. devcoach-0.3.6/tests/test_web_extra.py +388 -0
  35. {devcoach-0.1.0 → devcoach-0.3.6}/uv.lock +509 -1
  36. devcoach-0.1.0/.github/workflows/ci.yml +0 -110
  37. {devcoach-0.1.0 → devcoach-0.3.6}/.github/dependabot.yml +0 -0
  38. {devcoach-0.1.0 → devcoach-0.3.6}/.gitignore +0 -0
  39. {devcoach-0.1.0 → devcoach-0.3.6}/LICENSE +0 -0
  40. {devcoach-0.1.0 → devcoach-0.3.6}/NOTICE +0 -0
  41. {devcoach-0.1.0 → devcoach-0.3.6}/SKILL.md +0 -0
  42. {devcoach-0.1.0 → devcoach-0.3.6}/docs/PLAN.md +0 -0
  43. {devcoach-0.1.0 → devcoach-0.3.6}/docs/cli.md +0 -0
  44. {devcoach-0.1.0 → devcoach-0.3.6}/docs/configuration.md +0 -0
  45. {devcoach-0.1.0 → devcoach-0.3.6}/docs/getting-started.md +0 -0
  46. {devcoach-0.1.0 → devcoach-0.3.6}/docs/mcp-server.md +0 -0
  47. {devcoach-0.1.0 → devcoach-0.3.6}/docs/web-ui.md +0 -0
  48. {devcoach-0.1.0 → devcoach-0.3.6}/src/devcoach/SKILL.md +0 -0
  49. {devcoach-0.1.0 → devcoach-0.3.6}/src/devcoach/__init__.py +0 -0
  50. {devcoach-0.1.0 → devcoach-0.3.6}/src/devcoach/cli/__init__.py +0 -0
  51. {devcoach-0.1.0 → devcoach-0.3.6}/src/devcoach/cli/commands.py +0 -0
  52. {devcoach-0.1.0 → devcoach-0.3.6}/src/devcoach/core/__init__.py +0 -0
  53. {devcoach-0.1.0 → devcoach-0.3.6}/src/devcoach/core/coach.py +0 -0
  54. {devcoach-0.1.0 → devcoach-0.3.6}/src/devcoach/core/detect.py +0 -0
  55. {devcoach-0.1.0 → devcoach-0.3.6}/src/devcoach/core/git.py +0 -0
  56. {devcoach-0.1.0 → devcoach-0.3.6}/src/devcoach/core/prompts.py +0 -0
  57. {devcoach-0.1.0 → devcoach-0.3.6}/src/devcoach/mcp/__init__.py +0 -0
  58. {devcoach-0.1.0 → devcoach-0.3.6}/src/devcoach/mcp/server.py +0 -0
  59. {devcoach-0.1.0 → devcoach-0.3.6}/src/devcoach/web/__init__.py +0 -0
  60. {devcoach-0.1.0 → devcoach-0.3.6}/src/devcoach/web/static/favicon.svg +0 -0
  61. {devcoach-0.1.0 → devcoach-0.3.6}/src/devcoach/web/static/relative-time.js +0 -0
  62. {devcoach-0.1.0 → devcoach-0.3.6}/src/devcoach/web/static/style.css +0 -0
  63. {devcoach-0.1.0 → devcoach-0.3.6}/src/devcoach/web/static/vendor/alpinejs.min.js +0 -0
  64. {devcoach-0.1.0 → devcoach-0.3.6}/src/devcoach/web/static/vendor/flatpickr-dark.min.css +0 -0
  65. {devcoach-0.1.0 → devcoach-0.3.6}/src/devcoach/web/static/vendor/flatpickr.min.css +0 -0
  66. {devcoach-0.1.0 → devcoach-0.3.6}/src/devcoach/web/static/vendor/flatpickr.min.js +0 -0
  67. {devcoach-0.1.0 → devcoach-0.3.6}/src/devcoach/web/static/vendor/highlight.min.js +0 -0
  68. {devcoach-0.1.0 → devcoach-0.3.6}/src/devcoach/web/static/vendor/hljs-dark.min.css +0 -0
  69. {devcoach-0.1.0 → devcoach-0.3.6}/src/devcoach/web/static/vendor/hljs-light.min.css +0 -0
  70. {devcoach-0.1.0 → devcoach-0.3.6}/src/devcoach/web/static/vendor/htmx.min.js +0 -0
  71. {devcoach-0.1.0 → devcoach-0.3.6}/src/devcoach/web/static/vendor/icons/bitbucket.svg +0 -0
  72. {devcoach-0.1.0 → devcoach-0.3.6}/src/devcoach/web/static/vendor/icons/github.svg +0 -0
  73. {devcoach-0.1.0 → devcoach-0.3.6}/src/devcoach/web/static/vendor/icons/gitlab.svg +0 -0
  74. {devcoach-0.1.0 → devcoach-0.3.6}/src/devcoach/web/static/vendor/icons/vscode.svg +0 -0
  75. {devcoach-0.1.0 → devcoach-0.3.6}/src/devcoach/web/static/vendor/marked.min.js +0 -0
  76. {devcoach-0.1.0 → devcoach-0.3.6}/src/devcoach/web/static/vendor/tailwind.js +0 -0
  77. {devcoach-0.1.0 → devcoach-0.3.6}/src/devcoach/web/templates/lesson_detail.html +0 -0
  78. {devcoach-0.1.0 → devcoach-0.3.6}/tests/__init__.py +0 -0
  79. {devcoach-0.1.0 → devcoach-0.3.6}/tests/conftest.py +0 -0
  80. {devcoach-0.1.0 → devcoach-0.3.6}/tests/test_cli.py +0 -0
  81. {devcoach-0.1.0 → devcoach-0.3.6}/tests/test_web.py +0 -0
@@ -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
@@ -14,9 +14,9 @@ jobs:
14
14
  name: ruff check --fix + ruff format
15
15
  runs-on: ubuntu-latest
16
16
  steps:
17
- - uses: actions/checkout@v4
17
+ - uses: actions/checkout@v6
18
18
 
19
- - uses: astral-sh/setup-uv@v5
19
+ - uses: astral-sh/setup-uv@v7
20
20
  with:
21
21
  python-version: "3.12"
22
22
 
@@ -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
- - All code is **type-annotated** (mypy-compatible)
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.1.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
- [![PyPI](https://img.shields.io/pypi/v/devcoach)](https://pypi.org/project/devcoach/)
236
- [![Python](https://img.shields.io/pypi/pyversions/devcoach)](https://pypi.org/project/devcoach/)
235
+ [![PyPI](https://img.shields.io/github/v/release/UltimaPhoenix/dev-coach?label=PyPI)](https://pypi.org/project/devcoach/)
236
+ [![Python](https://img.shields.io/badge/python-3.11%2B-blue)](https://pypi.org/project/devcoach/)
237
237
  [![CI](https://github.com/UltimaPhoenix/dev-coach/actions/workflows/ci.yml/badge.svg)](https://github.com/UltimaPhoenix/dev-coach/actions/workflows/ci.yml)
238
+ [![Quality Gate](https://sonarcloud.io/api/project_badges/measure?project=UltimaPhoenix_dev-coach&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=UltimaPhoenix_dev-coach)
239
+ [![Coverage](https://sonarcloud.io/api/project_badges/measure?project=UltimaPhoenix_dev-coach&metric=coverage)](https://sonarcloud.io/summary/new_code?id=UltimaPhoenix_dev-coach)
240
+ [![Docs](https://img.shields.io/badge/docs-GitHub%20Pages-purple)](https://ultimaphoenix.github.io/dev-coach/)
238
241
  [![License](https://img.shields.io/badge/license-Apache%202.0-blue)](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
+ | ![Knowledge map](docs/screenshots/knowledge-map-light.png) | ![Lessons](docs/screenshots/lessons-dark.png) | ![Settings](docs/screenshots/settings-dark.png) |
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
- [![PyPI](https://img.shields.io/pypi/v/devcoach)](https://pypi.org/project/devcoach/)
4
- [![Python](https://img.shields.io/pypi/pyversions/devcoach)](https://pypi.org/project/devcoach/)
3
+ [![PyPI](https://img.shields.io/github/v/release/UltimaPhoenix/dev-coach?label=PyPI)](https://pypi.org/project/devcoach/)
4
+ [![Python](https://img.shields.io/badge/python-3.11%2B-blue)](https://pypi.org/project/devcoach/)
5
5
  [![CI](https://github.com/UltimaPhoenix/dev-coach/actions/workflows/ci.yml/badge.svg)](https://github.com/UltimaPhoenix/dev-coach/actions/workflows/ci.yml)
6
+ [![Quality Gate](https://sonarcloud.io/api/project_badges/measure?project=UltimaPhoenix_dev-coach&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=UltimaPhoenix_dev-coach)
7
+ [![Coverage](https://sonarcloud.io/api/project_badges/measure?project=UltimaPhoenix_dev-coach&metric=coverage)](https://sonarcloud.io/summary/new_code?id=UltimaPhoenix_dev-coach)
8
+ [![Docs](https://img.shields.io/badge/docs-GitHub%20Pages-purple)](https://ultimaphoenix.github.io/dev-coach/)
6
9
  [![License](https://img.shields.io/badge/license-Apache%202.0-blue)](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
+ | ![Knowledge map](docs/screenshots/knowledge-map-light.png) | ![Lessons](docs/screenshots/lessons-dark.png) | ![Settings](docs/screenshots/settings-dark.png) |
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
+ ![Knowledge map – dark theme](screenshots/knowledge-map-dark.png)
26
+
27
+ === "Light"
28
+ ![Knowledge map – light theme](screenshots/knowledge-map-light.png)
29
+
30
+ ### Lesson history
31
+
32
+ === "Dark"
33
+ ![Lessons – dark theme](screenshots/lessons-dark.png)
34
+
35
+ === "Light"
36
+ ![Lessons – light theme](screenshots/lessons-light.png)
37
+
38
+ ### Settings
39
+
40
+ === "Dark"
41
+ ![Settings – dark theme](screenshots/settings-dark.png)
42
+
43
+ === "Light"
44
+ ![Settings – light theme](screenshots/settings-dark.png)
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.
@@ -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.1.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/*"]
@@ -0,0 +1,11 @@
1
+ sonar.projectKey=UltimaPhoenix_dev-coach
2
+ sonar.organization=ultimaphoenix
3
+
4
+ sonar.projectName=devcoach
5
+ sonar.projectVersion=0.3.6
6
+
7
+ sonar.sources=src
8
+ sonar.tests=tests
9
+ sonar.python.version=3.11
10
+
11
+ sonar.python.coverage.reportPaths=coverage.xml