devcoach 0.3.8__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 (101) 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.8 → devcoach-0.3.9}/.github/scripts/take_screenshots.py +18 -19
  4. {devcoach-0.3.8 → devcoach-0.3.9}/.github/workflows/ci.yml +1 -1
  5. {devcoach-0.3.8 → devcoach-0.3.9}/.github/workflows/ruff-autofix.yml +9 -14
  6. {devcoach-0.3.8 → devcoach-0.3.9}/PKG-INFO +1 -1
  7. devcoach-0.3.9/docs/how-it-works.md +112 -0
  8. {devcoach-0.3.8 → devcoach-0.3.9}/docs/index.md +32 -6
  9. devcoach-0.3.9/docs/screenshots/knowledge-map-dark.png +0 -0
  10. devcoach-0.3.9/docs/screenshots/knowledge-map-light.png +0 -0
  11. devcoach-0.3.9/docs/screenshots/lesson-ci-cd-pipeline-stages-dark.png +0 -0
  12. devcoach-0.3.9/docs/screenshots/lesson-ci-cd-pipeline-stages-light.png +0 -0
  13. devcoach-0.3.9/docs/screenshots/lesson-docker-layer-cache-dark.png +0 -0
  14. devcoach-0.3.9/docs/screenshots/lesson-docker-layer-cache-light.png +0 -0
  15. devcoach-0.3.9/docs/screenshots/lesson-git-interactive-rebase-dark.png +0 -0
  16. devcoach-0.3.9/docs/screenshots/lesson-git-interactive-rebase-light.png +0 -0
  17. devcoach-0.3.9/docs/screenshots/lesson-postgresql-explain-analyze-dark.png +0 -0
  18. devcoach-0.3.9/docs/screenshots/lesson-postgresql-explain-analyze-light.png +0 -0
  19. devcoach-0.3.9/docs/screenshots/lesson-redis-cache-stampede-dark.png +0 -0
  20. devcoach-0.3.9/docs/screenshots/lesson-redis-cache-stampede-light.png +0 -0
  21. devcoach-0.3.9/docs/screenshots/lessons-dark.png +0 -0
  22. devcoach-0.3.9/docs/screenshots/lessons-light.png +0 -0
  23. devcoach-0.3.9/docs/screenshots/settings-dark.png +0 -0
  24. devcoach-0.3.9/docs/screenshots/settings-light.png +0 -0
  25. devcoach-0.3.9/docs/web-ui.md +144 -0
  26. {devcoach-0.3.8 → devcoach-0.3.9}/mkdocs.yml +7 -1
  27. {devcoach-0.3.8 → devcoach-0.3.9}/pyproject.toml +1 -1
  28. {devcoach-0.3.8 → devcoach-0.3.9}/sonar-project.properties +1 -1
  29. devcoach-0.3.9/src/devcoach/SKILL.md +490 -0
  30. {devcoach-0.3.8 → devcoach-0.3.9}/src/devcoach/cli/commands.py +15 -4
  31. {devcoach-0.3.8 → devcoach-0.3.9}/src/devcoach/core/coach.py +9 -10
  32. {devcoach-0.3.8 → devcoach-0.3.9}/src/devcoach/core/db.py +49 -20
  33. {devcoach-0.3.8 → devcoach-0.3.9}/src/devcoach/core/git.py +9 -11
  34. {devcoach-0.3.8 → devcoach-0.3.9}/src/devcoach/core/models.py +3 -43
  35. {devcoach-0.3.8 → devcoach-0.3.9}/src/devcoach/mcp/server.py +178 -99
  36. {devcoach-0.3.8 → devcoach-0.3.9}/src/devcoach/web/static/style.css +1 -0
  37. {devcoach-0.3.8 → devcoach-0.3.9}/src/devcoach/web/templates/lesson_detail.html +11 -5
  38. {devcoach-0.3.8 → devcoach-0.3.9}/tests/test_cli.py +1 -1
  39. {devcoach-0.3.8 → devcoach-0.3.9}/tests/test_mcp_server.py +125 -80
  40. {devcoach-0.3.8 → devcoach-0.3.9}/uv.lock +77 -77
  41. devcoach-0.3.8/.github/scripts/fixtures/devcoach-backup.zip +0 -0
  42. devcoach-0.3.8/docs/screenshots/knowledge-map-dark.png +0 -0
  43. devcoach-0.3.8/docs/screenshots/knowledge-map-light.png +0 -0
  44. devcoach-0.3.8/docs/screenshots/lessons-dark.png +0 -0
  45. devcoach-0.3.8/docs/screenshots/lessons-light.png +0 -0
  46. devcoach-0.3.8/docs/screenshots/settings-dark.png +0 -0
  47. devcoach-0.3.8/docs/screenshots/settings-light.png +0 -0
  48. devcoach-0.3.8/docs/web-ui.md +0 -88
  49. devcoach-0.3.8/src/devcoach/SKILL.md +0 -285
  50. {devcoach-0.3.8 → devcoach-0.3.9}/.github/dependabot.yml +0 -0
  51. {devcoach-0.3.8 → devcoach-0.3.9}/.github/workflows/update-screenshots.yml +0 -0
  52. {devcoach-0.3.8 → devcoach-0.3.9}/.gitignore +0 -0
  53. {devcoach-0.3.8 → devcoach-0.3.9}/CLAUDE.md +0 -0
  54. {devcoach-0.3.8 → devcoach-0.3.9}/LICENSE +0 -0
  55. {devcoach-0.3.8 → devcoach-0.3.9}/NOTICE +0 -0
  56. {devcoach-0.3.8 → devcoach-0.3.9}/README.md +0 -0
  57. {devcoach-0.3.8 → devcoach-0.3.9}/SKILL.md +0 -0
  58. {devcoach-0.3.8 → devcoach-0.3.9}/docs/PLAN.md +0 -0
  59. {devcoach-0.3.8 → devcoach-0.3.9}/docs/cli.md +0 -0
  60. {devcoach-0.3.8 → devcoach-0.3.9}/docs/configuration.md +0 -0
  61. {devcoach-0.3.8 → devcoach-0.3.9}/docs/favicon.svg +0 -0
  62. {devcoach-0.3.8 → devcoach-0.3.9}/docs/getting-started.md +0 -0
  63. {devcoach-0.3.8 → devcoach-0.3.9}/docs/mcp-server.md +0 -0
  64. {devcoach-0.3.8 → devcoach-0.3.9}/src/devcoach/__init__.py +0 -0
  65. {devcoach-0.3.8 → devcoach-0.3.9}/src/devcoach/cli/__init__.py +0 -0
  66. {devcoach-0.3.8 → devcoach-0.3.9}/src/devcoach/core/__init__.py +0 -0
  67. {devcoach-0.3.8 → devcoach-0.3.9}/src/devcoach/core/detect.py +0 -0
  68. {devcoach-0.3.8 → devcoach-0.3.9}/src/devcoach/core/prompts.py +0 -0
  69. {devcoach-0.3.8 → devcoach-0.3.9}/src/devcoach/mcp/__init__.py +0 -0
  70. {devcoach-0.3.8 → devcoach-0.3.9}/src/devcoach/web/__init__.py +0 -0
  71. {devcoach-0.3.8 → devcoach-0.3.9}/src/devcoach/web/app.py +0 -0
  72. {devcoach-0.3.8 → devcoach-0.3.9}/src/devcoach/web/static/favicon.svg +0 -0
  73. {devcoach-0.3.8 → devcoach-0.3.9}/src/devcoach/web/static/relative-time.js +0 -0
  74. {devcoach-0.3.8 → devcoach-0.3.9}/src/devcoach/web/static/vendor/alpinejs.min.js +0 -0
  75. {devcoach-0.3.8 → devcoach-0.3.9}/src/devcoach/web/static/vendor/flatpickr-dark.min.css +0 -0
  76. {devcoach-0.3.8 → devcoach-0.3.9}/src/devcoach/web/static/vendor/flatpickr.min.css +0 -0
  77. {devcoach-0.3.8 → devcoach-0.3.9}/src/devcoach/web/static/vendor/flatpickr.min.js +0 -0
  78. {devcoach-0.3.8 → devcoach-0.3.9}/src/devcoach/web/static/vendor/highlight.min.js +0 -0
  79. {devcoach-0.3.8 → devcoach-0.3.9}/src/devcoach/web/static/vendor/hljs-dark.min.css +0 -0
  80. {devcoach-0.3.8 → devcoach-0.3.9}/src/devcoach/web/static/vendor/hljs-light.min.css +0 -0
  81. {devcoach-0.3.8 → devcoach-0.3.9}/src/devcoach/web/static/vendor/htmx.min.js +0 -0
  82. {devcoach-0.3.8 → devcoach-0.3.9}/src/devcoach/web/static/vendor/icons/bitbucket.svg +0 -0
  83. {devcoach-0.3.8 → devcoach-0.3.9}/src/devcoach/web/static/vendor/icons/github.svg +0 -0
  84. {devcoach-0.3.8 → devcoach-0.3.9}/src/devcoach/web/static/vendor/icons/gitlab.svg +0 -0
  85. {devcoach-0.3.8 → devcoach-0.3.9}/src/devcoach/web/static/vendor/icons/vscode.svg +0 -0
  86. {devcoach-0.3.8 → devcoach-0.3.9}/src/devcoach/web/static/vendor/marked.min.js +0 -0
  87. {devcoach-0.3.8 → devcoach-0.3.9}/src/devcoach/web/static/vendor/tailwind.js +0 -0
  88. {devcoach-0.3.8 → devcoach-0.3.9}/src/devcoach/web/templates/base.html +0 -0
  89. {devcoach-0.3.8 → devcoach-0.3.9}/src/devcoach/web/templates/lessons.html +0 -0
  90. {devcoach-0.3.8 → devcoach-0.3.9}/src/devcoach/web/templates/profile.html +0 -0
  91. {devcoach-0.3.8 → devcoach-0.3.9}/src/devcoach/web/templates/settings.html +0 -0
  92. {devcoach-0.3.8 → devcoach-0.3.9}/tests/__init__.py +0 -0
  93. {devcoach-0.3.8 → devcoach-0.3.9}/tests/conftest.py +0 -0
  94. {devcoach-0.3.8 → devcoach-0.3.9}/tests/test_cli_commands.py +0 -0
  95. {devcoach-0.3.8 → devcoach-0.3.9}/tests/test_coach.py +0 -0
  96. {devcoach-0.3.8 → devcoach-0.3.9}/tests/test_db_extra.py +0 -0
  97. {devcoach-0.3.8 → devcoach-0.3.9}/tests/test_detect.py +0 -0
  98. {devcoach-0.3.8 → devcoach-0.3.9}/tests/test_git.py +0 -0
  99. {devcoach-0.3.8 → devcoach-0.3.9}/tests/test_prompts.py +0 -0
  100. {devcoach-0.3.8 → devcoach-0.3.9}/tests/test_web.py +0 -0
  101. {devcoach-0.3.8 → 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
 
@@ -202,7 +202,7 @@ jobs:
202
202
  - run: uv sync --group dev
203
203
  - name: Run tests with coverage
204
204
  run: uv run pytest tests/ -v --tb=short --cov=src/devcoach --cov-report=xml
205
- - uses: sonarsource/sonarqube-scan-action@v7
205
+ - uses: sonarsource/sonarqube-scan-action@v8
206
206
  env:
207
207
  SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
208
208
 
@@ -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@v8
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
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devcoach
3
- Version: 0.3.8
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
@@ -0,0 +1,112 @@
1
+ # How it works
2
+
3
+ devcoach is a silent technical coach that hooks into every Claude response.
4
+ The diagrams below show the three main flows: session startup, the coaching loop,
5
+ and how a lesson topic is selected.
6
+
7
+ ---
8
+
9
+ ## Session startup
10
+
11
+ At the start of each Claude session devcoach checks whether the user is set up,
12
+ loads prior coaching context, and primes lesson selection before any task is done.
13
+
14
+ ```mermaid
15
+ flowchart TD
16
+ A([Session starts]) --> B[Read devcoach://onboarding]
17
+ B --> C{needs_onboarding?}
18
+ C -- Yes --> D{Existing backup\nto restore?}
19
+ D -- Yes --> E[Restore backup\n→ mark onboarding done]
20
+ D -- No --> F[Detect stack\nautomatically or manually]
21
+ F --> G[Confirm topics + confidence\nPropose groups]
22
+ G --> H[complete_onboarding]
23
+ E & H --> I
24
+ C -- No --> I[Read ~/.devcoach/learning-state.md]
25
+ I --> J{Notebook\nnon-empty?}
26
+ J -- Yes --> K[Load patterns, hypotheses\nand recommended angles]
27
+ J -- No --> L[No prior context\nstart fresh]
28
+ K & L --> M([Ready to coach])
29
+ ```
30
+
31
+ ---
32
+
33
+ ## Coaching loop
34
+
35
+ After every technical task Claude evaluates whether to deliver a lesson.
36
+ The loop is silent when nothing is worth teaching or when the rate limit is reached.
37
+
38
+ ```mermaid
39
+ flowchart TD
40
+ A([Technical task\ncompleted]) --> B[Read devcoach://rate-limit]
41
+ B --> C{Allowed?}
42
+ C -- No\nnormal task --> Z([Silent —\nno lesson])
43
+ C -- No\nexplicit request --> D
44
+ C -- Yes --> D[Read profile\ntaught topics\ncoaching notebook]
45
+ D --> E[Analyse task for\nteachable concepts]
46
+ E --> F[Select topic\nsee Lesson selection]
47
+ F --> G{Topic\nfound?}
48
+ G -- No --> Z
49
+ G -- Yes --> H[Compose lesson\ncalibrate depth per-topic]
50
+ H --> I[log_lesson to MCP]
51
+ I --> J([Lesson appears\nat end of response])
52
+ J --> K{User\nfeedback}
53
+ K -- ✅ know --> L{Confidence\nbelow level band?}
54
+ L -- Yes --> M[submit_feedback\nconfidence +1]
55
+ L -- No --> N[Skip — already calibrated]
56
+ K -- ❌ don't know --> O[submit_feedback\nconfidence −1]
57
+ K -- ⏭ skip --> P[No change]
58
+ M & N & O & P --> Q{New observation\nworth saving?}
59
+ Q -- Yes --> R[Write ~/.devcoach/\nlearning-state.md]
60
+ Q -- No --> S
61
+ R --> S([Loop ends])
62
+ ```
63
+
64
+ ---
65
+
66
+ ## Lesson selection
67
+
68
+ When a teachable concept is found, devcoach picks the highest-priority angle
69
+ and calibrates the lesson level to the **per-topic** confidence score — not an average.
70
+
71
+ ```mermaid
72
+ flowchart TD
73
+ A([Concepts identified\nin current task]) --> B{Notebook flags\na follow-up angle\nrelevant to this task?}
74
+ B -- Yes --> P1["① Deliver notebook\nfollow-up"]
75
+
76
+ B -- No --> C{Pitfall on a\nprofile topic?}
77
+ C -- Yes --> P2["② Profile pitfall"]
78
+
79
+ C -- No --> D{Interesting pattern\non a profile topic?}
80
+ D -- Yes --> P3["③ Profile pattern"]
81
+
82
+ D -- No --> E{Off-profile concept\nprominent in task?}
83
+ E -- Yes --> P4["④ Off-profile pitfall"]
84
+
85
+ E -- No --> F{Profile topic with\nconfidence < 5?}
86
+ F -- Yes --> P5["⑤ Knowledge gap"]
87
+
88
+ F -- No --> G{Profile topic at\nconfidence 4–6?}
89
+ G -- Yes --> P6["⑥ Deep-dive"]
90
+
91
+ G -- No --> Z([Nothing to teach\n— stay silent])
92
+
93
+ P1 & P2 & P3 & P4 & P5 & P6 --> L[Check taught-topics\nno repeats]
94
+ L --> M{Already taught\nor confidence ≥ 10?}
95
+ M -- Already taught\nnot confidence 10 --> Z
96
+ M -- OK or\nconfidence = 10 --> N[Calibrate level\nper-topic confidence]
97
+ N --> O([Compose and\ndeliver lesson])
98
+ ```
99
+
100
+ ---
101
+
102
+ ## Depth calibration
103
+
104
+ The lesson level is determined by the confidence score for the **specific topic being taught**,
105
+ adjusted by observations in the coaching notebook.
106
+
107
+ | Confidence | Level | Lesson angle |
108
+ |---|---|---|
109
+ | 0 – 3 | Junior | Introduce correct practice, explain from scratch, use analogies |
110
+ | 4 – 6 | Mid | Explain the why, mention trade-offs and alternatives |
111
+ | 7 – 9 | Senior | Edge cases, historical context, architectural implications |
112
+ | 10 | Cutting-edge | Latest developments — ignores level floor and taught-topics filter |
@@ -8,12 +8,30 @@ Everything runs **locally**. No data leaves your machine. One SQLite file at `~/
8
8
 
9
9
  ## How it works
10
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 |
11
+ ```mermaid
12
+ sequenceDiagram
13
+ actor User
14
+ participant Claude as Claude (AI)
15
+ participant devcoach as devcoach (MCP)
16
+
17
+ User->>+Claude: Complete a technical task
18
+ Claude-->>User: Work completed normally
19
+
20
+ Claude->>devcoach: check rate-limit + profile + taught topics
21
+ devcoach-->>Claude: knowledge map · lesson history · coaching notebook
22
+
23
+ Claude->>Claude: select topic · calibrate depth · compose lesson
24
+
25
+ Claude->>devcoach: log_lesson(id, topic, level, body, …)
26
+ Claude-->>-User: Response + 🎓 lesson at the bottom
27
+
28
+ User->>+Claude: ✅ know · ❌ don't know · ⏭ skip
29
+ Claude->>devcoach: submit_feedback → confidence ±1
30
+ Claude->>Claude: update coaching notebook if warranted
31
+ Claude-->>-User: acknowledged
32
+ ```
33
+
34
+ See [How it works](how-it-works.md) for the full decision flow.
17
35
 
18
36
  ---
19
37
 
@@ -43,6 +61,14 @@ Everything runs **locally**. No data leaves your machine. One SQLite file at `~/
43
61
  === "Light"
44
62
  ![Settings – light theme](screenshots/settings-light.png)
45
63
 
64
+ ### Lesson detail
65
+
66
+ === "Dark"
67
+ ![Lesson detail – dark theme](screenshots/lesson-redis-cache-stampede-dark.png)
68
+
69
+ === "Light"
70
+ ![Lesson detail – light theme](screenshots/lesson-redis-cache-stampede-light.png)
71
+
46
72
  ---
47
73
 
48
74
  ## Quick install
@@ -0,0 +1,144 @@
1
+ # Web UI
2
+
3
+ The devcoach web dashboard provides a visual interface for everything the CLI does.
4
+
5
+ ## Launch
6
+
7
+ ```bash
8
+ devcoach ui # http://localhost:7860
9
+ devcoach ui --port 8080 # custom port
10
+ ```
11
+
12
+ Or via the MCP tool (Claude can launch it for you):
13
+
14
+ ```
15
+ open_ui({ port: 7860 })
16
+ ```
17
+
18
+ ---
19
+
20
+ ## Pages
21
+
22
+ ### Knowledge map (`/`)
23
+
24
+ Displays your full knowledge map with colour-coded confidence bars:
25
+
26
+ - **Green** — confidence ≥ 7 (strong)
27
+ - **Yellow** — confidence 4–6 (intermediate)
28
+ - **Red** — confidence ≤ 3 (learning)
29
+
30
+ **Edit mode** (click `✎ Edit`):
31
+ - `+` / `−` buttons beside each bar to adjust confidence by 1
32
+ - `⇄` button to move a topic to a different group
33
+ - `×` button to delete a topic
34
+ - `+ topic` button in each group header to add a topic directly to that group
35
+ - `+ Add group` / `+ Add topic` buttons in the page header
36
+ - `×` beside group names to delete a group (topics move to Other)
37
+
38
+ **View mode**: topic names are clickable links that filter the lessons page to that topic.
39
+
40
+ **Stats bar** (top of page): total lessons, today's count vs. daily limit, this week's count, and current rate-limit status.
41
+
42
+ === "Dark"
43
+ ![Knowledge map – dark theme](screenshots/knowledge-map-dark.png)
44
+
45
+ === "Light"
46
+ ![Knowledge map – light theme](screenshots/knowledge-map-light.png)
47
+
48
+ ---
49
+
50
+ ### Lessons (`/lessons`)
51
+
52
+ Filterable, sortable table of all delivered lessons.
53
+
54
+ **Filters:**
55
+ - Period (today / week / month / year / all)
56
+ - Category tag
57
+ - Difficulty level
58
+ - Project / repository / branch / commit hash
59
+ - Starred only
60
+ - Feedback (know / dont_know / none)
61
+ - Free-text search
62
+ - Date range (supports optional time: `2026-04-25T14:30`)
63
+
64
+ **Sort:** by timestamp, level, topic, title, or feedback. Ascending or descending.
65
+
66
+ **Pagination:** 25 per page.
67
+
68
+ **Actions per lesson:**
69
+ - `★` — toggle starred
70
+ - Feedback buttons (✓ / ✗ / clear) — record comprehension, adjusts knowledge confidence
71
+ - Lesson ID link — opens the detail page
72
+
73
+ === "Dark"
74
+ ![Lessons – dark theme](screenshots/lessons-dark.png)
75
+
76
+ === "Light"
77
+ ![Lessons – light theme](screenshots/lessons-light.png)
78
+
79
+ ---
80
+
81
+ ### Lesson detail (`/lessons/<id>`)
82
+
83
+ Full lesson content laid out in reading order:
84
+
85
+ - **Title row** — star toggle, title, level badge (junior / mid / senior)
86
+ - **Metadata row** — relative date with tooltip, topic ID, category tags, feedback badge + clear button
87
+ - **TL;DR callout** — one-sentence summary in a highlighted indigo box, always visible above the body
88
+ - **Lesson body** — full markdown content with syntax-highlighted code blocks
89
+ - **Task context** — the coding task that triggered the lesson (when available)
90
+ - **Git metadata** — project, repository (with platform icon + link), branch, commit hash, folder (VSCode deep-link)
91
+ - **Feedback buttons** — ✓ I know this / ✗ I don't know this (hidden once feedback is recorded)
92
+
93
+ === "Docker layer caching (Junior)"
94
+ === "Dark"
95
+ ![Docker layer caching – dark](screenshots/lesson-docker-layer-cache-dark.png)
96
+ === "Light"
97
+ ![Docker layer caching – light](screenshots/lesson-docker-layer-cache-light.png)
98
+
99
+ === "PostgreSQL EXPLAIN ANALYZE (Mid)"
100
+ === "Dark"
101
+ ![PostgreSQL EXPLAIN ANALYZE – dark](screenshots/lesson-postgresql-explain-analyze-dark.png)
102
+ === "Light"
103
+ ![PostgreSQL EXPLAIN ANALYZE – light](screenshots/lesson-postgresql-explain-analyze-light.png)
104
+
105
+ === "Git interactive rebase (Mid)"
106
+ === "Dark"
107
+ ![Git interactive rebase – dark](screenshots/lesson-git-interactive-rebase-dark.png)
108
+ === "Light"
109
+ ![Git interactive rebase – light](screenshots/lesson-git-interactive-rebase-light.png)
110
+
111
+ === "CI/CD pipeline stages (Senior)"
112
+ === "Dark"
113
+ ![CI/CD pipeline stages – dark](screenshots/lesson-ci-cd-pipeline-stages-dark.png)
114
+ === "Light"
115
+ ![CI/CD pipeline stages – light](screenshots/lesson-ci-cd-pipeline-stages-light.png)
116
+
117
+ === "Cache stampede (Senior)"
118
+ === "Dark"
119
+ ![Cache stampede – dark](screenshots/lesson-redis-cache-stampede-dark.png)
120
+ === "Light"
121
+ ![Cache stampede – light](screenshots/lesson-redis-cache-stampede-light.png)
122
+
123
+ ---
124
+
125
+ ### Settings (`/settings`)
126
+
127
+ - **Max per day** — maximum lessons in a 24-hour window (1–20)
128
+ - **Min gap** — minimum minutes between lessons (0–1440), input as hours + minutes
129
+ - **Export lessons** — download all lessons as JSON
130
+ - **Import lessons** — upload a previously exported JSON file
131
+ - **Export backup** — full zip (settings + knowledge + lessons)
132
+ - **Import backup** — restore from a backup zip
133
+
134
+ === "Dark"
135
+ ![Settings – dark theme](screenshots/settings-dark.png)
136
+
137
+ === "Light"
138
+ ![Settings – light theme](screenshots/settings-light.png)
139
+
140
+ ---
141
+
142
+ ## Keyboard shortcuts
143
+
144
+ The web UI has no keyboard shortcuts. Use the CLI for faster access to individual commands.
@@ -27,12 +27,14 @@ theme:
27
27
  - navigation.indexes
28
28
  - search.highlight
29
29
  - content.code.copy
30
+ - content.tabs.link
30
31
  - toc.integrate
31
32
  icon:
32
33
  repo: fontawesome/brands/github
33
34
 
34
35
  nav:
35
36
  - Home: index.md
37
+ - How it works: how-it-works.md
36
38
  - Getting started: getting-started.md
37
39
  - CLI reference: cli.md
38
40
  - MCP server: mcp-server.md
@@ -46,7 +48,11 @@ markdown_extensions:
46
48
  - admonition
47
49
  - pymdownx.highlight:
48
50
  anchor_linenums: true
49
- - pymdownx.superfences
51
+ - pymdownx.superfences:
52
+ custom_fences:
53
+ - name: mermaid
54
+ class: mermaid
55
+ format: !!python/name:pymdownx.superfences.fence_code_format
50
56
  - pymdownx.tabbed:
51
57
  alternate_style: true
52
58
  - tables
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "devcoach"
3
- version = "0.3.8"
3
+ version = "0.3.9"
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.12"
@@ -2,7 +2,7 @@ sonar.projectKey=UltimaPhoenix_dev-coach
2
2
  sonar.organization=ultimaphoenix
3
3
 
4
4
  sonar.projectName=devcoach
5
- sonar.projectVersion=0.3.8
5
+ sonar.projectVersion=0.3.9
6
6
 
7
7
  sonar.sources=src
8
8
  sonar.tests=tests