devcoach 0.3.6__tar.gz → 0.3.9__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 (102) hide show
  1. devcoach-0.3.9/.github/scripts/export_backup.py +138 -0
  2. devcoach-0.3.9/.github/scripts/fixtures/devcoach-backup.zip +0 -0
  3. {devcoach-0.3.6 → devcoach-0.3.9}/.github/scripts/take_screenshots.py +18 -19
  4. {devcoach-0.3.6 → devcoach-0.3.9}/.github/workflows/ci.yml +41 -13
  5. {devcoach-0.3.6 → devcoach-0.3.9}/.github/workflows/ruff-autofix.yml +9 -14
  6. {devcoach-0.3.6 → devcoach-0.3.9}/CLAUDE.md +4 -3
  7. {devcoach-0.3.6 → devcoach-0.3.9}/PKG-INFO +15 -13
  8. {devcoach-0.3.6 → devcoach-0.3.9}/README.md +13 -10
  9. {devcoach-0.3.6 → devcoach-0.3.9}/docs/cli.md +38 -4
  10. {devcoach-0.3.6 → devcoach-0.3.9}/docs/getting-started.md +14 -4
  11. devcoach-0.3.9/docs/how-it-works.md +112 -0
  12. devcoach-0.3.9/docs/index.md +81 -0
  13. {devcoach-0.3.6 → devcoach-0.3.9}/docs/mcp-server.md +9 -4
  14. devcoach-0.3.9/docs/screenshots/knowledge-map-dark.png +0 -0
  15. devcoach-0.3.9/docs/screenshots/knowledge-map-light.png +0 -0
  16. devcoach-0.3.9/docs/screenshots/lesson-ci-cd-pipeline-stages-dark.png +0 -0
  17. devcoach-0.3.9/docs/screenshots/lesson-ci-cd-pipeline-stages-light.png +0 -0
  18. devcoach-0.3.9/docs/screenshots/lesson-docker-layer-cache-dark.png +0 -0
  19. devcoach-0.3.9/docs/screenshots/lesson-docker-layer-cache-light.png +0 -0
  20. devcoach-0.3.9/docs/screenshots/lesson-git-interactive-rebase-dark.png +0 -0
  21. devcoach-0.3.9/docs/screenshots/lesson-git-interactive-rebase-light.png +0 -0
  22. devcoach-0.3.9/docs/screenshots/lesson-postgresql-explain-analyze-dark.png +0 -0
  23. devcoach-0.3.9/docs/screenshots/lesson-postgresql-explain-analyze-light.png +0 -0
  24. devcoach-0.3.9/docs/screenshots/lesson-redis-cache-stampede-dark.png +0 -0
  25. devcoach-0.3.9/docs/screenshots/lesson-redis-cache-stampede-light.png +0 -0
  26. devcoach-0.3.9/docs/screenshots/lessons-dark.png +0 -0
  27. devcoach-0.3.9/docs/screenshots/lessons-light.png +0 -0
  28. devcoach-0.3.9/docs/screenshots/settings-dark.png +0 -0
  29. devcoach-0.3.9/docs/screenshots/settings-light.png +0 -0
  30. devcoach-0.3.9/docs/web-ui.md +144 -0
  31. {devcoach-0.3.6 → devcoach-0.3.9}/mkdocs.yml +7 -1
  32. {devcoach-0.3.6 → devcoach-0.3.9}/pyproject.toml +3 -4
  33. {devcoach-0.3.6 → devcoach-0.3.9}/sonar-project.properties +2 -2
  34. devcoach-0.3.9/src/devcoach/SKILL.md +490 -0
  35. {devcoach-0.3.6 → devcoach-0.3.9}/src/devcoach/cli/commands.py +189 -30
  36. {devcoach-0.3.6 → devcoach-0.3.9}/src/devcoach/core/coach.py +7 -8
  37. {devcoach-0.3.6 → devcoach-0.3.9}/src/devcoach/core/db.py +52 -23
  38. {devcoach-0.3.6 → devcoach-0.3.9}/src/devcoach/core/models.py +1 -0
  39. {devcoach-0.3.6 → devcoach-0.3.9}/src/devcoach/mcp/server.py +163 -151
  40. {devcoach-0.3.6 → devcoach-0.3.9}/src/devcoach/web/app.py +6 -2
  41. {devcoach-0.3.6 → devcoach-0.3.9}/src/devcoach/web/static/style.css +1 -0
  42. {devcoach-0.3.6 → devcoach-0.3.9}/src/devcoach/web/templates/lesson_detail.html +12 -5
  43. {devcoach-0.3.6 → devcoach-0.3.9}/src/devcoach/web/templates/lessons.html +1 -0
  44. {devcoach-0.3.6 → devcoach-0.3.9}/tests/test_cli.py +31 -10
  45. {devcoach-0.3.6 → devcoach-0.3.9}/tests/test_cli_commands.py +269 -29
  46. {devcoach-0.3.6 → devcoach-0.3.9}/tests/test_mcp_server.py +152 -107
  47. {devcoach-0.3.6 → devcoach-0.3.9}/tests/test_web.py +24 -8
  48. {devcoach-0.3.6 → devcoach-0.3.9}/uv.lock +79 -316
  49. devcoach-0.3.6/.github/scripts/fixtures/devcoach-backup.zip +0 -0
  50. devcoach-0.3.6/docs/index.md +0 -55
  51. devcoach-0.3.6/docs/screenshots/knowledge-map-dark.png +0 -0
  52. devcoach-0.3.6/docs/screenshots/knowledge-map-light.png +0 -0
  53. devcoach-0.3.6/docs/screenshots/lessons-dark.png +0 -0
  54. devcoach-0.3.6/docs/screenshots/lessons-light.png +0 -0
  55. devcoach-0.3.6/docs/screenshots/settings-dark.png +0 -0
  56. devcoach-0.3.6/docs/screenshots/settings-light.png +0 -0
  57. devcoach-0.3.6/docs/web-ui.md +0 -88
  58. devcoach-0.3.6/src/devcoach/SKILL.md +0 -288
  59. {devcoach-0.3.6 → devcoach-0.3.9}/.github/dependabot.yml +0 -0
  60. {devcoach-0.3.6 → devcoach-0.3.9}/.github/workflows/update-screenshots.yml +0 -0
  61. {devcoach-0.3.6 → devcoach-0.3.9}/.gitignore +0 -0
  62. {devcoach-0.3.6 → devcoach-0.3.9}/LICENSE +0 -0
  63. {devcoach-0.3.6 → devcoach-0.3.9}/NOTICE +0 -0
  64. {devcoach-0.3.6 → devcoach-0.3.9}/SKILL.md +0 -0
  65. {devcoach-0.3.6 → devcoach-0.3.9}/docs/PLAN.md +0 -0
  66. {devcoach-0.3.6 → devcoach-0.3.9}/docs/configuration.md +0 -0
  67. {devcoach-0.3.6 → devcoach-0.3.9}/docs/favicon.svg +0 -0
  68. {devcoach-0.3.6 → devcoach-0.3.9}/src/devcoach/__init__.py +0 -0
  69. {devcoach-0.3.6 → devcoach-0.3.9}/src/devcoach/cli/__init__.py +0 -0
  70. {devcoach-0.3.6 → devcoach-0.3.9}/src/devcoach/core/__init__.py +0 -0
  71. {devcoach-0.3.6 → devcoach-0.3.9}/src/devcoach/core/detect.py +0 -0
  72. {devcoach-0.3.6 → devcoach-0.3.9}/src/devcoach/core/git.py +0 -0
  73. {devcoach-0.3.6 → devcoach-0.3.9}/src/devcoach/core/prompts.py +0 -0
  74. {devcoach-0.3.6 → devcoach-0.3.9}/src/devcoach/mcp/__init__.py +0 -0
  75. {devcoach-0.3.6 → devcoach-0.3.9}/src/devcoach/web/__init__.py +0 -0
  76. {devcoach-0.3.6 → devcoach-0.3.9}/src/devcoach/web/static/favicon.svg +0 -0
  77. {devcoach-0.3.6 → devcoach-0.3.9}/src/devcoach/web/static/relative-time.js +0 -0
  78. {devcoach-0.3.6 → devcoach-0.3.9}/src/devcoach/web/static/vendor/alpinejs.min.js +0 -0
  79. {devcoach-0.3.6 → devcoach-0.3.9}/src/devcoach/web/static/vendor/flatpickr-dark.min.css +0 -0
  80. {devcoach-0.3.6 → devcoach-0.3.9}/src/devcoach/web/static/vendor/flatpickr.min.css +0 -0
  81. {devcoach-0.3.6 → devcoach-0.3.9}/src/devcoach/web/static/vendor/flatpickr.min.js +0 -0
  82. {devcoach-0.3.6 → devcoach-0.3.9}/src/devcoach/web/static/vendor/highlight.min.js +0 -0
  83. {devcoach-0.3.6 → devcoach-0.3.9}/src/devcoach/web/static/vendor/hljs-dark.min.css +0 -0
  84. {devcoach-0.3.6 → devcoach-0.3.9}/src/devcoach/web/static/vendor/hljs-light.min.css +0 -0
  85. {devcoach-0.3.6 → devcoach-0.3.9}/src/devcoach/web/static/vendor/htmx.min.js +0 -0
  86. {devcoach-0.3.6 → devcoach-0.3.9}/src/devcoach/web/static/vendor/icons/bitbucket.svg +0 -0
  87. {devcoach-0.3.6 → devcoach-0.3.9}/src/devcoach/web/static/vendor/icons/github.svg +0 -0
  88. {devcoach-0.3.6 → devcoach-0.3.9}/src/devcoach/web/static/vendor/icons/gitlab.svg +0 -0
  89. {devcoach-0.3.6 → devcoach-0.3.9}/src/devcoach/web/static/vendor/icons/vscode.svg +0 -0
  90. {devcoach-0.3.6 → devcoach-0.3.9}/src/devcoach/web/static/vendor/marked.min.js +0 -0
  91. {devcoach-0.3.6 → devcoach-0.3.9}/src/devcoach/web/static/vendor/tailwind.js +0 -0
  92. {devcoach-0.3.6 → devcoach-0.3.9}/src/devcoach/web/templates/base.html +0 -0
  93. {devcoach-0.3.6 → devcoach-0.3.9}/src/devcoach/web/templates/profile.html +0 -0
  94. {devcoach-0.3.6 → devcoach-0.3.9}/src/devcoach/web/templates/settings.html +0 -0
  95. {devcoach-0.3.6 → devcoach-0.3.9}/tests/__init__.py +0 -0
  96. {devcoach-0.3.6 → devcoach-0.3.9}/tests/conftest.py +0 -0
  97. {devcoach-0.3.6 → devcoach-0.3.9}/tests/test_coach.py +0 -0
  98. {devcoach-0.3.6 → devcoach-0.3.9}/tests/test_db_extra.py +0 -0
  99. {devcoach-0.3.6 → devcoach-0.3.9}/tests/test_detect.py +0 -0
  100. {devcoach-0.3.6 → devcoach-0.3.9}/tests/test_git.py +0 -0
  101. {devcoach-0.3.6 → devcoach-0.3.9}/tests/test_prompts.py +0 -0
  102. {devcoach-0.3.6 → devcoach-0.3.9}/tests/test_web_extra.py +0 -0
@@ -0,0 +1,138 @@
1
+ """Anonymize a devcoach backup zip for safe public sharing.
2
+
3
+ Replacements applied to lessons.json:
4
+ - Absolute paths (/root/username/...) → /home/user/...
5
+ - Repository owner prefix (owner/repo) → developer/repo
6
+ - Commit hashes (full 40-char SHA-1) → random 40-char hex
7
+
8
+ Usage:
9
+ python .github/scripts/anonymize_backup.py <input.zip> [output.zip]
10
+
11
+ If output.zip is omitted the anonymized file is written next to the input
12
+ with "-anon" appended to the stem (e.g. devcoach-backup-anon.zip).
13
+ """
14
+
15
+ import json
16
+ import re
17
+ import secrets
18
+ import sys
19
+ import zipfile
20
+ from pathlib import Path
21
+
22
+
23
+ SHA1_RE = re.compile(r"\b[0-9a-f]{40}\b", re.IGNORECASE)
24
+ REPO_USER_RE = re.compile(r"^[^/\s]+/")
25
+
26
+ ANON_REPO_PREFIX = "developer/"
27
+
28
+
29
+ def _random_sha1() -> str:
30
+ return secrets.token_hex(20)
31
+
32
+
33
+ def _anonymize_path(path: str) -> str:
34
+ """Replace username in any absolute path /root/username/... → /home/user/..."""
35
+ parts = path.split("/")
36
+ # Absolute path: ['', '<root>', '<username>', ...]
37
+ if len(parts) >= 3 and parts[0] == "" and parts[1] and parts[2]:
38
+ parts[1] = "home"
39
+ parts[2] = "user"
40
+ return "/".join(parts)
41
+
42
+
43
+ def _has_personal_path(value: str) -> bool:
44
+ """True if value contains an absolute path whose username is not 'user'."""
45
+ parts = value.split("/")
46
+ return (
47
+ len(parts) >= 3
48
+ and parts[0] == ""
49
+ and parts[1] != ""
50
+ and parts[2] not in ("", "user")
51
+ and "/" in value[1:] # at least one slash after the leading one
52
+ )
53
+
54
+
55
+ def anonymize_lesson(lesson: dict, original_hashes: set[str]) -> dict:
56
+ out = dict(lesson)
57
+
58
+ if out.get("folder"):
59
+ out["folder"] = _anonymize_path(out["folder"])
60
+
61
+ if out.get("repository"):
62
+ out["repository"] = REPO_USER_RE.sub(ANON_REPO_PREFIX, out["repository"])
63
+
64
+ if out.get("commit_hash"):
65
+ original_hashes.add(out["commit_hash"].lower())
66
+ out["commit_hash"] = _random_sha1()
67
+
68
+ if out.get("task_context"):
69
+ ctx = out["task_context"]
70
+
71
+ def _replace_hash(m: re.Match) -> str:
72
+ original_hashes.add(m.group().lower())
73
+ return _random_sha1()
74
+
75
+ ctx = SHA1_RE.sub(_replace_hash, ctx)
76
+ tokens = ctx.split()
77
+ ctx = " ".join(_anonymize_path(t) if _has_personal_path(t) else t for t in tokens)
78
+ out["task_context"] = ctx
79
+
80
+ return out
81
+
82
+
83
+ def anonymize_zip(src: Path, dst: Path) -> None:
84
+ original_hashes: set[str] = set()
85
+
86
+ with zipfile.ZipFile(src) as zin, zipfile.ZipFile(dst, "w", zipfile.ZIP_DEFLATED) as zout:
87
+ for name in zin.namelist():
88
+ if name == "learning-state.md":
89
+ continue # personal coaching narrative — excluded from anon export
90
+
91
+ raw = zin.read(name)
92
+
93
+ if name == "lessons.json":
94
+ lessons = json.loads(raw.decode())
95
+ lessons = [anonymize_lesson(l, original_hashes) for l in lessons]
96
+ raw = json.dumps(lessons, indent=2, ensure_ascii=False).encode()
97
+
98
+ zout.writestr(name, raw)
99
+
100
+ print(f"Anonymized backup written to: {dst}")
101
+ _verify(dst, original_hashes)
102
+
103
+
104
+ def _verify(path: Path, original_hashes: set[str]) -> None:
105
+ with zipfile.ZipFile(path) as z:
106
+ lessons = json.loads(z.read("lessons.json").decode())
107
+ issues: list[str] = []
108
+ for lesson in lessons:
109
+ for field in ("folder", "repository", "commit_hash", "task_context"):
110
+ val = lesson.get(field) or ""
111
+ if _has_personal_path(val):
112
+ issues.append(f" {lesson['id']}.{field}: personal path still present")
113
+ for match in SHA1_RE.finditer(val):
114
+ if match.group().lower() in original_hashes:
115
+ issues.append(f" {lesson['id']}.{field}: original SHA-1 hash still present")
116
+ if issues:
117
+ print("Verification warnings:")
118
+ for w in issues:
119
+ print(w)
120
+ else:
121
+ print(f"Verification passed ({len(lessons)} lessons, no personal data detected).")
122
+
123
+
124
+ def main() -> None:
125
+ if len(sys.argv) < 2:
126
+ sys.exit(f"Usage: python {sys.argv[0]} <input.zip> [output.zip]")
127
+
128
+ src = Path(sys.argv[1])
129
+ if not src.exists():
130
+ sys.exit(f"File not found: {src}")
131
+
132
+ dst = Path(sys.argv[2]) if len(sys.argv) > 2 else src.with_stem(src.stem + "-anon")
133
+
134
+ anonymize_zip(src, dst)
135
+
136
+
137
+ if __name__ == "__main__":
138
+ main()
@@ -27,6 +27,14 @@ PAGES = [
27
27
  ("settings", "/settings"),
28
28
  ]
29
29
 
30
+ LESSON_PAGES = [
31
+ ("lesson-docker-layer-cache", "/lessons/lesson-docker-layer-cache-001"),
32
+ ("lesson-postgresql-explain-analyze", "/lessons/lesson-postgresql-explain-analyze-001"),
33
+ ("lesson-git-interactive-rebase", "/lessons/lesson-git-interactive-rebase-001"),
34
+ ("lesson-ci-cd-pipeline-stages", "/lessons/lesson-ci-cd-pipeline-stages-001"),
35
+ ("lesson-redis-cache-stampede", "/lessons/lesson-redis-cache-stampede-001"),
36
+ ]
37
+
30
38
  def restore_db() -> None:
31
39
  result = subprocess.run(
32
40
  ["devcoach", "restore", str(BACKUP_ZIP)],
@@ -55,25 +63,16 @@ def take_screenshots(server_proc: subprocess.Popen) -> None:
55
63
  with sync_playwright() as pw:
56
64
  browser = pw.chromium.launch()
57
65
 
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()
66
+ for scheme in ("light", "dark"):
67
+ ctx = browser.new_context(viewport=VIEWPORT, color_scheme=scheme)
68
+ page = ctx.new_page()
69
+ for name, path in PAGES + LESSON_PAGES:
70
+ page.goto(f"{BASE_URL}{path}")
71
+ page.wait_for_load_state("networkidle")
72
+ out = SCREENSHOTS_DIR / f"{name}-{scheme}.png"
73
+ page.screenshot(path=str(out))
74
+ print(f" saved {out}")
75
+ ctx.close()
77
76
 
78
77
  browser.close()
79
78
 
@@ -117,7 +117,7 @@ jobs:
117
117
  strategy:
118
118
  fail-fast: false
119
119
  matrix:
120
- python-version: ["3.11", "3.12", "3.13"]
120
+ python-version: ["3.12", "3.13"]
121
121
  steps:
122
122
  - uses: actions/checkout@v6
123
123
  with:
@@ -149,14 +149,47 @@ jobs:
149
149
  path: dist/
150
150
  retention-days: 7
151
151
 
152
+ # ── Coverage comment on PR ──────────────────────────────────────────────
153
+ # Posts (and updates) a coverage summary comment on every PR push.
154
+ # Reads the .coverage file generated by pytest-cov — no extra deps needed.
155
+ coverage:
156
+ name: Coverage comment
157
+ needs: [bump]
158
+ if: >
159
+ github.event_name == 'pull_request'
160
+ && (needs.bump.result == 'success' || needs.bump.result == 'skipped')
161
+ runs-on: ubuntu-latest
162
+ permissions:
163
+ pull-requests: write
164
+ steps:
165
+ - uses: actions/checkout@v6
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/ --cov=src/devcoach --cov-report=xml
172
+ - uses: py-cov-action/python-coverage-comment-action@v3
173
+ with:
174
+ GITHUB_TOKEN: ${{ github.token }}
175
+ MINIMUM_GREEN: 90
176
+ MINIMUM_ORANGE: 80
177
+
152
178
  # ── 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).
179
+ # CI-based analysis: feeds coverage.xml to SonarCloud.
180
+ # Skipped on pull_request events SONAR_TOKEN is not available to PR
181
+ # workflows (GitHub restricts secret access). Runs on main / tags / dispatch
182
+ # where the token is available.
183
+ # Requires Automatic Analysis to be DISABLED on sonarcloud.io
184
+ # (Administration → Analysis Method).
156
185
  sonar:
157
186
  name: SonarCloud scan
158
187
  needs: [bump, test]
159
- if: always() && (needs.bump.result == 'success' || needs.bump.result == 'skipped') && needs.test.result == 'success'
188
+ if: >
189
+ always()
190
+ && github.event_name != 'pull_request'
191
+ && (needs.bump.result == 'success' || needs.bump.result == 'skipped')
192
+ && needs.test.result == 'success'
160
193
  runs-on: ubuntu-latest
161
194
  steps:
162
195
  - uses: actions/checkout@v6
@@ -169,12 +202,7 @@ jobs:
169
202
  - run: uv sync --group dev
170
203
  - name: Run tests with coverage
171
204
  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
205
+ - uses: sonarsource/sonarqube-scan-action@v8
178
206
  env:
179
207
  SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
180
208
 
@@ -198,12 +226,12 @@ jobs:
198
226
  python-version: "3.12"
199
227
  - run: uv sync --group docs
200
228
  - run: uv run mkdocs build
201
- - uses: actions/upload-pages-artifact@v3
229
+ - uses: actions/upload-pages-artifact@v5
202
230
  with:
203
231
  path: site/
204
232
  - name: Deploy to GitHub Pages
205
233
  id: deploy
206
- uses: actions/deploy-pages@v4
234
+ uses: actions/deploy-pages@v5
207
235
 
208
236
  # ── Publish ─────────────────────────────────────────────────────────────
209
237
  # Runs when a tag is present: either created by the bump job (workflow_dispatch)
@@ -7,7 +7,6 @@ on:
7
7
 
8
8
  permissions:
9
9
  contents: write
10
- pull-requests: write
11
10
 
12
11
  jobs:
13
12
  autofix:
@@ -29,16 +28,12 @@ jobs:
29
28
  - name: Run ruff format
30
29
  run: uv run ruff format src/ tests/
31
30
 
32
- - name: Open PR if there are changes
33
- uses: peter-evans/create-pull-request@v7
34
- with:
35
- commit-message: "style: apply ruff auto-fixes"
36
- title: "style: ruff auto-fix"
37
- body: |
38
- Automated style fixes applied by `ruff check --fix` and `ruff format`.
39
-
40
- This PR was opened automatically by the **Ruff auto-fix** workflow.
41
- Review the diff, then merge or close.
42
- branch: ruff/autofix
43
- delete-branch: true
44
- labels: style
31
+ - name: Commit and push if there are changes
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 -A
36
+ git diff --staged --quiet && echo "No changes." || (
37
+ git commit -m "style: apply ruff auto-fixes [skip ci]" &&
38
+ git push
39
+ )
@@ -9,7 +9,7 @@ based on the user's knowledge map, the rate limit, and what has already been tau
9
9
 
10
10
  Repo: https://github.com/UltimaPhoenix/dev-coach
11
11
  PyPI package name: `devcoach`
12
- End-user command: `uvx devcoach`
12
+ End-user command: `uvx devcoach` (CLI) / `uvx devcoach mcp` (MCP server)
13
13
 
14
14
  ---
15
15
 
@@ -240,7 +240,7 @@ npx @modelcontextprotocol/inspector devcoach
240
240
  "mcpServers": {
241
241
  "devcoach": {
242
242
  "command": "uvx",
243
- "args": ["devcoach"]
243
+ "args": ["devcoach", "mcp"]
244
244
  }
245
245
  }
246
246
  }
@@ -252,7 +252,8 @@ npx @modelcontextprotocol/inspector devcoach
252
252
 
253
253
  - Follow **Uncle Bob's Clean Code** principles
254
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
255
+ - Linting and formatting enforced by **ruff** — run `uv run ruff check src/ tests/` and `uv run ruff format src/ tests/` before committing. **All ruff checks must pass before committing.**
256
+ - Test coverage must stay **at or above 80%** — run `uv run pytest --cov=src/devcoach --cov-fail-under=80` to verify. Do not merge code that drops total coverage below this threshold.
256
257
  - No external dependencies beyond `fastmcp` and `pydantic`
257
258
  - `db.py` exposes only pure functions — no business logic
258
259
  - `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.6
3
+ Version: 0.3.9
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
@@ -216,12 +216,11 @@ Classifier: Intended Audience :: Developers
216
216
  Classifier: License :: OSI Approved :: Apache Software License
217
217
  Classifier: Operating System :: OS Independent
218
218
  Classifier: Programming Language :: Python :: 3
219
- Classifier: Programming Language :: Python :: 3.11
220
219
  Classifier: Programming Language :: Python :: 3.12
221
220
  Classifier: Programming Language :: Python :: 3.13
222
221
  Classifier: Topic :: Education
223
222
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
224
- Requires-Python: >=3.11
223
+ Requires-Python: >=3.12
225
224
  Requires-Dist: fastapi>=0.110
226
225
  Requires-Dist: fastmcp>=2.0
227
226
  Requires-Dist: jinja2>=3.1
@@ -233,7 +232,7 @@ Description-Content-Type: text/markdown
233
232
  # devcoach
234
233
 
235
234
  [![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/)
235
+ [![Python](https://img.shields.io/badge/python-3.12%2B-blue)](https://pypi.org/project/devcoach/)
237
236
  [![CI](https://github.com/UltimaPhoenix/dev-coach/actions/workflows/ci.yml/badge.svg)](https://github.com/UltimaPhoenix/dev-coach/actions/workflows/ci.yml)
238
237
  [![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
238
  [![Coverage](https://sonarcloud.io/api/project_badges/measure?project=UltimaPhoenix_dev-coach&metric=coverage)](https://sonarcloud.io/summary/new_code?id=UltimaPhoenix_dev-coach)
@@ -259,9 +258,9 @@ Everything runs **locally**. No data leaves your machine. One SQLite file at `~/
259
258
 
260
259
  ## Screenshots
261
260
 
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) |
261
+ | Knowledge map | Lesson history | Settings |
262
+ |:---------------------------------------------------------:|:---:|:---:|
263
+ | ![Knowledge map](docs/screenshots/knowledge-map-dark.png) | ![Lessons](docs/screenshots/lessons-dark.png) | ![Settings](docs/screenshots/settings-dark.png) |
265
264
 
266
265
  ---
267
266
 
@@ -270,7 +269,7 @@ Everything runs **locally**. No data leaves your machine. One SQLite file at `~/
270
269
  ### Recommended — no permanent install needed
271
270
 
272
271
  ```bash
273
- uvx devcoach
272
+ uvx devcoach mcp # starts the MCP server directly
274
273
  ```
275
274
 
276
275
  ### Permanent install
@@ -287,7 +286,7 @@ devcoach install
287
286
 
288
287
  Restart Claude Code or Claude Desktop after installing.
289
288
 
290
- > **Requirements:** [uv](https://docs.astral.sh/uv/) · Python 3.11+ · Claude Code or Claude Desktop
289
+ > **Requirements:** [uv](https://docs.astral.sh/uv/) · Python 3.12+ · Claude Code or Claude Desktop
291
290
 
292
291
  ---
293
292
 
@@ -322,7 +321,7 @@ Claude: [does the work]
322
321
 
323
322
  **Structured concurrency with asyncio.TaskGroup**
324
323
 
325
- TaskGroup (Python 3.11+) is the modern replacement for bare gather() calls.
324
+ TaskGroup (Python 3.12+) is the modern replacement for bare gather() calls.
326
325
  Unlike gather(), it cancels sibling tasks automatically when one raises...
327
326
  ```
328
327
 
@@ -341,13 +340,16 @@ devcoach feedback lesson-python-taskgroup-001 dont_know # need to revisit —
341
340
 
342
341
  | Command | Description |
343
342
  |---------|-------------|
343
+ | `devcoach` | Show all available commands |
344
+ | `devcoach mcp` | Start the MCP server (stdio) for Claude Code / Claude Desktop |
344
345
  | `devcoach setup` | Run the onboarding wizard in the terminal |
345
346
  | `devcoach install` | Register with Claude Code / Claude Desktop |
346
347
  | `devcoach profile` | Show your knowledge map with confidence bars |
347
348
  | `devcoach stats` | Overview: lesson counts, weakest/strongest topics |
348
349
  | `devcoach lessons` | Browse lesson history with filters |
349
350
  | `devcoach lesson <id>` | Show a single lesson in full |
350
- | `devcoach star <id>` | Toggle starred flag |
351
+ | `devcoach star <id>` | Mark a lesson as starred (favourite) |
352
+ | `devcoach unstar <id>` | Remove the starred mark from a lesson |
351
353
  | `devcoach feedback <id> <know\|dont_know\|clear>` | Record comprehension |
352
354
  | `devcoach set max_per_day <n>` | Max lessons in a 24-hour window (default 2) |
353
355
  | `devcoach set min_gap_minutes <n>` | Minimum minutes between lessons (default 240) |
@@ -387,7 +389,7 @@ devcoach implements the [MCP 2025-11-25 spec](https://modelcontextprotocol.io/sp
387
389
  "devcoach": {
388
390
  "type": "stdio",
389
391
  "command": "uvx",
390
- "args": ["devcoach"]
392
+ "args": ["devcoach", "mcp"]
391
393
  }
392
394
  }
393
395
  }
@@ -429,7 +431,7 @@ git tag v1.2.3
429
431
  git push origin v1.2.3
430
432
  ```
431
433
 
432
- The pipeline will lint, test across Python 3.11–3.13, build, publish to PyPI via OIDC Trusted Publishing, and create a GitHub Release automatically.
434
+ The pipeline will lint, test across Python 3.12–3.13, build, publish to PyPI via OIDC Trusted Publishing, and create a GitHub Release automatically.
433
435
 
434
436
  > **First-time PyPI setup:** configure a Trusted Publisher on PyPI for `UltimaPhoenix/dev-coach` (environment: `pypi`, workflow: `ci.yml`). No API token required after that.
435
437
 
@@ -1,7 +1,7 @@
1
1
  # devcoach
2
2
 
3
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/)
4
+ [![Python](https://img.shields.io/badge/python-3.12%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
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
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)
@@ -27,9 +27,9 @@ Everything runs **locally**. No data leaves your machine. One SQLite file at `~/
27
27
 
28
28
  ## Screenshots
29
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) |
30
+ | Knowledge map | Lesson history | Settings |
31
+ |:---------------------------------------------------------:|:---:|:---:|
32
+ | ![Knowledge map](docs/screenshots/knowledge-map-dark.png) | ![Lessons](docs/screenshots/lessons-dark.png) | ![Settings](docs/screenshots/settings-dark.png) |
33
33
 
34
34
  ---
35
35
 
@@ -38,7 +38,7 @@ Everything runs **locally**. No data leaves your machine. One SQLite file at `~/
38
38
  ### Recommended — no permanent install needed
39
39
 
40
40
  ```bash
41
- uvx devcoach
41
+ uvx devcoach mcp # starts the MCP server directly
42
42
  ```
43
43
 
44
44
  ### Permanent install
@@ -55,7 +55,7 @@ devcoach install
55
55
 
56
56
  Restart Claude Code or Claude Desktop after installing.
57
57
 
58
- > **Requirements:** [uv](https://docs.astral.sh/uv/) · Python 3.11+ · Claude Code or Claude Desktop
58
+ > **Requirements:** [uv](https://docs.astral.sh/uv/) · Python 3.12+ · Claude Code or Claude Desktop
59
59
 
60
60
  ---
61
61
 
@@ -90,7 +90,7 @@ Claude: [does the work]
90
90
 
91
91
  **Structured concurrency with asyncio.TaskGroup**
92
92
 
93
- TaskGroup (Python 3.11+) is the modern replacement for bare gather() calls.
93
+ TaskGroup (Python 3.12+) is the modern replacement for bare gather() calls.
94
94
  Unlike gather(), it cancels sibling tasks automatically when one raises...
95
95
  ```
96
96
 
@@ -109,13 +109,16 @@ devcoach feedback lesson-python-taskgroup-001 dont_know # need to revisit —
109
109
 
110
110
  | Command | Description |
111
111
  |---------|-------------|
112
+ | `devcoach` | Show all available commands |
113
+ | `devcoach mcp` | Start the MCP server (stdio) for Claude Code / Claude Desktop |
112
114
  | `devcoach setup` | Run the onboarding wizard in the terminal |
113
115
  | `devcoach install` | Register with Claude Code / Claude Desktop |
114
116
  | `devcoach profile` | Show your knowledge map with confidence bars |
115
117
  | `devcoach stats` | Overview: lesson counts, weakest/strongest topics |
116
118
  | `devcoach lessons` | Browse lesson history with filters |
117
119
  | `devcoach lesson <id>` | Show a single lesson in full |
118
- | `devcoach star <id>` | Toggle starred flag |
120
+ | `devcoach star <id>` | Mark a lesson as starred (favourite) |
121
+ | `devcoach unstar <id>` | Remove the starred mark from a lesson |
119
122
  | `devcoach feedback <id> <know\|dont_know\|clear>` | Record comprehension |
120
123
  | `devcoach set max_per_day <n>` | Max lessons in a 24-hour window (default 2) |
121
124
  | `devcoach set min_gap_minutes <n>` | Minimum minutes between lessons (default 240) |
@@ -155,7 +158,7 @@ devcoach implements the [MCP 2025-11-25 spec](https://modelcontextprotocol.io/sp
155
158
  "devcoach": {
156
159
  "type": "stdio",
157
160
  "command": "uvx",
158
- "args": ["devcoach"]
161
+ "args": ["devcoach", "mcp"]
159
162
  }
160
163
  }
161
164
  }
@@ -197,7 +200,7 @@ git tag v1.2.3
197
200
  git push origin v1.2.3
198
201
  ```
199
202
 
200
- The pipeline will lint, test across Python 3.11–3.13, build, publish to PyPI via OIDC Trusted Publishing, and create a GitHub Release automatically.
203
+ The pipeline will lint, test across Python 3.12–3.13, build, publish to PyPI via OIDC Trusted Publishing, and create a GitHub Release automatically.
201
204
 
202
205
  > **First-time PyPI setup:** configure a Trusted Publisher on PyPI for `UltimaPhoenix/dev-coach` (environment: `pypi`, workflow: `ci.yml`). No API token required after that.
203
206
 
@@ -1,15 +1,38 @@
1
1
  # CLI reference
2
2
 
3
- ## Global
3
+ ## Overview
4
+
5
+ Running `devcoach` with no arguments prints a help panel listing every available command:
4
6
 
5
7
  ```
6
- devcoach <command> [options]
8
+ devcoach
7
9
  ```
8
10
 
9
11
  All commands operate on `~/.devcoach/coaching.db`. No network access required.
10
12
 
11
13
  ---
12
14
 
15
+ ## MCP server
16
+
17
+ ### `devcoach mcp`
18
+
19
+ Start the stdio MCP server for Claude Code or Claude Desktop. This is what you put in your MCP config:
20
+
21
+ ```json
22
+ {
23
+ "mcpServers": {
24
+ "devcoach": {
25
+ "command": "uvx",
26
+ "args": ["devcoach", "mcp"]
27
+ }
28
+ }
29
+ }
30
+ ```
31
+
32
+ ---
33
+
34
+ ---
35
+
13
36
  ## Knowledge map
14
37
 
15
38
  ### `devcoach profile`
@@ -117,13 +140,24 @@ devcoach lesson lesson-python-generators-001
117
140
 
118
141
  ### `devcoach star <id>`
119
142
 
120
- Toggle the starred (favourite) flag on a lesson.
143
+ Mark a lesson as starred (favourite).
121
144
 
122
145
  ```bash
123
146
  devcoach star lesson-python-generators-001
124
147
  # → Lesson lesson-python-generators-001 → ★ starred
125
148
  ```
126
149
 
150
+ ### `devcoach unstar <id>`
151
+
152
+ Remove the starred mark from a lesson.
153
+
154
+ ```bash
155
+ devcoach unstar lesson-python-generators-001
156
+ # → Lesson lesson-python-generators-001 → ☆ unstarred
157
+ ```
158
+
159
+ Both commands are idempotent — calling them when the lesson is already in the target state is safe.
160
+
127
161
  ### `devcoach feedback <id> <value>`
128
162
 
129
163
  Record whether you understood a lesson. Adjusts knowledge confidence.
@@ -191,7 +225,7 @@ Followed by optional group assignment and rate-limit settings.
191
225
 
192
226
  ### `devcoach install`
193
227
 
194
- Register the devcoach MCP server in Claude's config files.
228
+ Register the devcoach MCP server (`devcoach mcp`) in Claude's config files.
195
229
 
196
230
  ```bash
197
231
  devcoach install # both Claude Code + Claude Desktop
@@ -3,25 +3,35 @@
3
3
  ## Prerequisites
4
4
 
5
5
  - [uv](https://docs.astral.sh/uv/) (recommended) or pip
6
- - Python 3.11+
6
+ - Python 3.12+
7
7
  - Claude Code or Claude Desktop
8
8
 
9
9
  ---
10
10
 
11
11
  ## 1. Install
12
12
 
13
- ### One-time run (no permanent install)
13
+ devcoach is published on [PyPI](https://pypi.org/project/devcoach/) and can be installed with any Python package manager.
14
+
15
+ ### One-time run — no install needed
14
16
 
15
17
  ```bash
16
- uvx devcoach
18
+ uvx devcoach mcp
17
19
  ```
18
20
 
19
- ### Permanent install
21
+ `uvx` fetches the latest release from PyPI and runs it in an isolated environment. Nothing is left behind.
22
+
23
+ ### Permanent install with uv (recommended)
20
24
 
21
25
  ```bash
22
26
  uv tool install devcoach
23
27
  ```
24
28
 
29
+ ### Permanent install with pip
30
+
31
+ ```bash
32
+ pip install devcoach
33
+ ```
34
+
25
35
  ---
26
36
 
27
37
  ## 2. Register with Claude