devcoach 0.3.8__tar.gz → 0.3.10__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.10/.github/scripts/export_backup.py +138 -0
  2. devcoach-0.3.10/.github/scripts/fixtures/devcoach-backup.zip +0 -0
  3. {devcoach-0.3.8 → devcoach-0.3.10}/.github/scripts/take_screenshots.py +18 -19
  4. {devcoach-0.3.8 → devcoach-0.3.10}/.github/workflows/ci.yml +1 -1
  5. {devcoach-0.3.8 → devcoach-0.3.10}/.github/workflows/ruff-autofix.yml +9 -14
  6. {devcoach-0.3.8 → devcoach-0.3.10}/.gitignore +2 -0
  7. {devcoach-0.3.8 → devcoach-0.3.10}/PKG-INFO +30 -7
  8. {devcoach-0.3.8 → devcoach-0.3.10}/README.md +29 -6
  9. devcoach-0.3.10/docs/how-it-works.md +102 -0
  10. {devcoach-0.3.8 → devcoach-0.3.10}/docs/index.md +37 -6
  11. devcoach-0.3.10/docs/screenshots/knowledge-map-dark.png +0 -0
  12. devcoach-0.3.10/docs/screenshots/knowledge-map-light.png +0 -0
  13. devcoach-0.3.10/docs/screenshots/lesson-ci-cd-pipeline-stages-dark.png +0 -0
  14. devcoach-0.3.10/docs/screenshots/lesson-ci-cd-pipeline-stages-light.png +0 -0
  15. devcoach-0.3.10/docs/screenshots/lesson-docker-layer-cache-dark.png +0 -0
  16. devcoach-0.3.10/docs/screenshots/lesson-docker-layer-cache-light.png +0 -0
  17. devcoach-0.3.10/docs/screenshots/lesson-git-interactive-rebase-dark.png +0 -0
  18. devcoach-0.3.10/docs/screenshots/lesson-git-interactive-rebase-light.png +0 -0
  19. devcoach-0.3.10/docs/screenshots/lesson-postgresql-explain-analyze-dark.png +0 -0
  20. devcoach-0.3.10/docs/screenshots/lesson-postgresql-explain-analyze-light.png +0 -0
  21. devcoach-0.3.10/docs/screenshots/lesson-redis-cache-stampede-dark.png +0 -0
  22. devcoach-0.3.10/docs/screenshots/lesson-redis-cache-stampede-light.png +0 -0
  23. devcoach-0.3.10/docs/screenshots/lessons-dark.png +0 -0
  24. devcoach-0.3.10/docs/screenshots/lessons-light.png +0 -0
  25. devcoach-0.3.10/docs/screenshots/settings-dark.png +0 -0
  26. devcoach-0.3.10/docs/screenshots/settings-light.png +0 -0
  27. devcoach-0.3.10/docs/web-ui.md +144 -0
  28. {devcoach-0.3.8 → devcoach-0.3.10}/mkdocs.yml +7 -1
  29. {devcoach-0.3.8 → devcoach-0.3.10}/pyproject.toml +1 -1
  30. {devcoach-0.3.8 → devcoach-0.3.10}/sonar-project.properties +1 -1
  31. devcoach-0.3.10/src/devcoach/SKILL.md +490 -0
  32. {devcoach-0.3.8 → devcoach-0.3.10}/src/devcoach/cli/commands.py +15 -4
  33. {devcoach-0.3.8 → devcoach-0.3.10}/src/devcoach/core/coach.py +9 -10
  34. {devcoach-0.3.8 → devcoach-0.3.10}/src/devcoach/core/db.py +49 -20
  35. {devcoach-0.3.8 → devcoach-0.3.10}/src/devcoach/core/git.py +9 -11
  36. {devcoach-0.3.8 → devcoach-0.3.10}/src/devcoach/core/models.py +3 -43
  37. {devcoach-0.3.8 → devcoach-0.3.10}/src/devcoach/mcp/server.py +221 -113
  38. {devcoach-0.3.8 → devcoach-0.3.10}/src/devcoach/web/static/style.css +1 -0
  39. {devcoach-0.3.8 → devcoach-0.3.10}/src/devcoach/web/templates/lesson_detail.html +11 -5
  40. {devcoach-0.3.8 → devcoach-0.3.10}/tests/test_cli.py +1 -1
  41. {devcoach-0.3.8 → devcoach-0.3.10}/tests/test_mcp_server.py +276 -80
  42. {devcoach-0.3.8 → devcoach-0.3.10}/uv.lock +77 -77
  43. devcoach-0.3.8/.github/scripts/fixtures/devcoach-backup.zip +0 -0
  44. devcoach-0.3.8/docs/screenshots/knowledge-map-dark.png +0 -0
  45. devcoach-0.3.8/docs/screenshots/knowledge-map-light.png +0 -0
  46. devcoach-0.3.8/docs/screenshots/lessons-dark.png +0 -0
  47. devcoach-0.3.8/docs/screenshots/lessons-light.png +0 -0
  48. devcoach-0.3.8/docs/screenshots/settings-dark.png +0 -0
  49. devcoach-0.3.8/docs/screenshots/settings-light.png +0 -0
  50. devcoach-0.3.8/docs/web-ui.md +0 -88
  51. devcoach-0.3.8/src/devcoach/SKILL.md +0 -285
  52. {devcoach-0.3.8 → devcoach-0.3.10}/.github/dependabot.yml +0 -0
  53. {devcoach-0.3.8 → devcoach-0.3.10}/.github/workflows/update-screenshots.yml +0 -0
  54. {devcoach-0.3.8 → devcoach-0.3.10}/CLAUDE.md +0 -0
  55. {devcoach-0.3.8 → devcoach-0.3.10}/LICENSE +0 -0
  56. {devcoach-0.3.8 → devcoach-0.3.10}/NOTICE +0 -0
  57. {devcoach-0.3.8 → devcoach-0.3.10}/SKILL.md +0 -0
  58. {devcoach-0.3.8 → devcoach-0.3.10}/docs/PLAN.md +0 -0
  59. {devcoach-0.3.8 → devcoach-0.3.10}/docs/cli.md +0 -0
  60. {devcoach-0.3.8 → devcoach-0.3.10}/docs/configuration.md +0 -0
  61. {devcoach-0.3.8 → devcoach-0.3.10}/docs/favicon.svg +0 -0
  62. {devcoach-0.3.8 → devcoach-0.3.10}/docs/getting-started.md +0 -0
  63. {devcoach-0.3.8 → devcoach-0.3.10}/docs/mcp-server.md +0 -0
  64. {devcoach-0.3.8 → devcoach-0.3.10}/src/devcoach/__init__.py +0 -0
  65. {devcoach-0.3.8 → devcoach-0.3.10}/src/devcoach/cli/__init__.py +0 -0
  66. {devcoach-0.3.8 → devcoach-0.3.10}/src/devcoach/core/__init__.py +0 -0
  67. {devcoach-0.3.8 → devcoach-0.3.10}/src/devcoach/core/detect.py +0 -0
  68. {devcoach-0.3.8 → devcoach-0.3.10}/src/devcoach/core/prompts.py +0 -0
  69. {devcoach-0.3.8 → devcoach-0.3.10}/src/devcoach/mcp/__init__.py +0 -0
  70. {devcoach-0.3.8 → devcoach-0.3.10}/src/devcoach/web/__init__.py +0 -0
  71. {devcoach-0.3.8 → devcoach-0.3.10}/src/devcoach/web/app.py +0 -0
  72. {devcoach-0.3.8 → devcoach-0.3.10}/src/devcoach/web/static/favicon.svg +0 -0
  73. {devcoach-0.3.8 → devcoach-0.3.10}/src/devcoach/web/static/relative-time.js +0 -0
  74. {devcoach-0.3.8 → devcoach-0.3.10}/src/devcoach/web/static/vendor/alpinejs.min.js +0 -0
  75. {devcoach-0.3.8 → devcoach-0.3.10}/src/devcoach/web/static/vendor/flatpickr-dark.min.css +0 -0
  76. {devcoach-0.3.8 → devcoach-0.3.10}/src/devcoach/web/static/vendor/flatpickr.min.css +0 -0
  77. {devcoach-0.3.8 → devcoach-0.3.10}/src/devcoach/web/static/vendor/flatpickr.min.js +0 -0
  78. {devcoach-0.3.8 → devcoach-0.3.10}/src/devcoach/web/static/vendor/highlight.min.js +0 -0
  79. {devcoach-0.3.8 → devcoach-0.3.10}/src/devcoach/web/static/vendor/hljs-dark.min.css +0 -0
  80. {devcoach-0.3.8 → devcoach-0.3.10}/src/devcoach/web/static/vendor/hljs-light.min.css +0 -0
  81. {devcoach-0.3.8 → devcoach-0.3.10}/src/devcoach/web/static/vendor/htmx.min.js +0 -0
  82. {devcoach-0.3.8 → devcoach-0.3.10}/src/devcoach/web/static/vendor/icons/bitbucket.svg +0 -0
  83. {devcoach-0.3.8 → devcoach-0.3.10}/src/devcoach/web/static/vendor/icons/github.svg +0 -0
  84. {devcoach-0.3.8 → devcoach-0.3.10}/src/devcoach/web/static/vendor/icons/gitlab.svg +0 -0
  85. {devcoach-0.3.8 → devcoach-0.3.10}/src/devcoach/web/static/vendor/icons/vscode.svg +0 -0
  86. {devcoach-0.3.8 → devcoach-0.3.10}/src/devcoach/web/static/vendor/marked.min.js +0 -0
  87. {devcoach-0.3.8 → devcoach-0.3.10}/src/devcoach/web/static/vendor/tailwind.js +0 -0
  88. {devcoach-0.3.8 → devcoach-0.3.10}/src/devcoach/web/templates/base.html +0 -0
  89. {devcoach-0.3.8 → devcoach-0.3.10}/src/devcoach/web/templates/lessons.html +0 -0
  90. {devcoach-0.3.8 → devcoach-0.3.10}/src/devcoach/web/templates/profile.html +0 -0
  91. {devcoach-0.3.8 → devcoach-0.3.10}/src/devcoach/web/templates/settings.html +0 -0
  92. {devcoach-0.3.8 → devcoach-0.3.10}/tests/__init__.py +0 -0
  93. {devcoach-0.3.8 → devcoach-0.3.10}/tests/conftest.py +0 -0
  94. {devcoach-0.3.8 → devcoach-0.3.10}/tests/test_cli_commands.py +0 -0
  95. {devcoach-0.3.8 → devcoach-0.3.10}/tests/test_coach.py +0 -0
  96. {devcoach-0.3.8 → devcoach-0.3.10}/tests/test_db_extra.py +0 -0
  97. {devcoach-0.3.8 → devcoach-0.3.10}/tests/test_detect.py +0 -0
  98. {devcoach-0.3.8 → devcoach-0.3.10}/tests/test_git.py +0 -0
  99. {devcoach-0.3.8 → devcoach-0.3.10}/tests/test_prompts.py +0 -0
  100. {devcoach-0.3.8 → devcoach-0.3.10}/tests/test_web.py +0 -0
  101. {devcoach-0.3.8 → devcoach-0.3.10}/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
+ )
@@ -197,6 +197,8 @@ cython_debug/
197
197
  # Claude Code
198
198
  .claude/
199
199
  .playwright-mcp/
200
+ .playwright-cli/
201
+ config.example.json
200
202
 
201
203
  # Cursor
202
204
  # Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devcoach
3
- Version: 0.3.8
3
+ Version: 0.3.10
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
@@ -245,12 +245,35 @@ Description-Content-Type: text/markdown
245
245
 
246
246
  ## How it works
247
247
 
248
- | Step | What happens |
249
- |------|-------------|
250
- | You complete a task with Claude | Claude finishes the work as normal |
251
- | devcoach checks your knowledge map | Finds a topic where you have room to grow, related to what you just did |
252
- | A lesson appears at the end of the response | Calibrated to your level (junior / mid / senior), never repeated |
253
- | You mark it know / don't know | Confidence scores update, shaping future lessons |
248
+ ```mermaid
249
+ flowchart TD
250
+ A([Task completed]) --> B[Check rate limit]
251
+ B -->|denied| Z([Silent])
252
+ B -->|allowed| D
253
+
254
+ subgraph loop["coaching loop"]
255
+ D[Select topic & depth]
256
+ E[Compose & deliver]
257
+ G[log_lesson]
258
+ end
259
+
260
+ D -->|nothing| Z
261
+ D -->|found| E
262
+ E --> G
263
+ G --> F([Done])
264
+ G -.->|prompts| U(["You: ✅ ❌ ⏭"])
265
+
266
+ style loop fill:none,stroke:#AAAAAA,stroke-dasharray:5 5,color:#757575
267
+ classDef action fill:#D4E4D8,stroke:#8BAF96,color:#1E1E1E
268
+ classDef term fill:#E8E8E4,stroke:#AAAAAA,color:#1E1E1E
269
+ classDef user fill:#F5EDE3,stroke:#D4A27F,color:#1E1E1E
270
+
271
+ class B,D,E,G action
272
+ class A,F,Z term
273
+ class U user
274
+ ```
275
+
276
+ → [Full decision flow: session startup · lesson selection · depth calibration](https://ultimaphoenix.github.io/dev-coach/how-it-works/)
254
277
 
255
278
  Everything runs **locally**. No data leaves your machine. One SQLite file at `~/.devcoach/coaching.db`.
256
279
 
@@ -14,12 +14,35 @@
14
14
 
15
15
  ## How it works
16
16
 
17
- | Step | What happens |
18
- |------|-------------|
19
- | You complete a task with Claude | Claude finishes the work as normal |
20
- | devcoach checks your knowledge map | Finds a topic where you have room to grow, related to what you just did |
21
- | A lesson appears at the end of the response | Calibrated to your level (junior / mid / senior), never repeated |
22
- | You mark it know / don't know | Confidence scores update, shaping future lessons |
17
+ ```mermaid
18
+ flowchart TD
19
+ A([Task completed]) --> B[Check rate limit]
20
+ B -->|denied| Z([Silent])
21
+ B -->|allowed| D
22
+
23
+ subgraph loop["coaching loop"]
24
+ D[Select topic & depth]
25
+ E[Compose & deliver]
26
+ G[log_lesson]
27
+ end
28
+
29
+ D -->|nothing| Z
30
+ D -->|found| E
31
+ E --> G
32
+ G --> F([Done])
33
+ G -.->|prompts| U(["You: ✅ ❌ ⏭"])
34
+
35
+ style loop fill:none,stroke:#AAAAAA,stroke-dasharray:5 5,color:#757575
36
+ classDef action fill:#D4E4D8,stroke:#8BAF96,color:#1E1E1E
37
+ classDef term fill:#E8E8E4,stroke:#AAAAAA,color:#1E1E1E
38
+ classDef user fill:#F5EDE3,stroke:#D4A27F,color:#1E1E1E
39
+
40
+ class B,D,E,G action
41
+ class A,F,Z term
42
+ class U user
43
+ ```
44
+
45
+ → [Full decision flow: session startup · lesson selection · depth calibration](https://ultimaphoenix.github.io/dev-coach/how-it-works/)
23
46
 
24
47
  Everything runs **locally**. No data leaves your machine. One SQLite file at `~/.devcoach/coaching.db`.
25
48
 
@@ -0,0 +1,102 @@
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 LR
16
+ A([Start]) --> B{First run?}
17
+ B -- yes --> C[Detect stack]
18
+ C --> D[Confirm topics\n& groups]
19
+ D --> E[Save profile]
20
+ B -- no --> F[Load profile\n& notebook]
21
+ E & F --> G([Ready])
22
+
23
+ subgraph onboarding["onboarding"]
24
+ C
25
+ D
26
+ E
27
+ end
28
+
29
+ style onboarding fill:none,stroke:#AAAAAA,stroke-dasharray:5 5,color:#757575
30
+ classDef action fill:#D4E4D8,stroke:#8BAF96,color:#1E1E1E
31
+ classDef term fill:#E8E8E4,stroke:#AAAAAA,color:#1E1E1E
32
+
33
+ class C,D,E,F action
34
+ class A,G term
35
+ ```
36
+
37
+ ---
38
+
39
+ ## Coaching loop
40
+
41
+ After every technical task Claude evaluates whether to deliver a lesson.
42
+ The loop is silent when nothing is worth teaching or when the rate limit is reached.
43
+
44
+ ```mermaid
45
+ flowchart TD
46
+ A([Task completed]) --> B[Check rate limit]
47
+ B -->|denied| Z([Silent])
48
+ B -->|allowed| D
49
+
50
+ subgraph loop["coaching loop"]
51
+ D[Select topic & depth]
52
+ E[Compose & deliver]
53
+ G[log_lesson]
54
+ end
55
+
56
+ D -->|nothing| Z
57
+ D -->|found| E
58
+ E --> G
59
+ G --> F([Done])
60
+ G -.->|prompts| U(["You: ✅ ❌ ⏭"])
61
+
62
+ style loop fill:none,stroke:#AAAAAA,stroke-dasharray:5 5,color:#757575
63
+ classDef action fill:#D4E4D8,stroke:#8BAF96,color:#1E1E1E
64
+ classDef term fill:#E8E8E4,stroke:#AAAAAA,color:#1E1E1E
65
+ classDef user fill:#F5EDE3,stroke:#D4A27F,color:#1E1E1E
66
+
67
+ class B,D,E,G action
68
+ class A,F,Z term
69
+ class U user
70
+ ```
71
+
72
+ ---
73
+
74
+ ## Lesson selection
75
+
76
+ When a teachable concept is found, devcoach walks this priority list from top to bottom
77
+ and picks the first match. Depth is then calibrated to the per-topic confidence score.
78
+
79
+ | Priority | Trigger | Condition |
80
+ |:---:|---|---|
81
+ | ① | Notebook follow-up | The coaching notebook flagged an angle relevant to the current task |
82
+ | ② | Profile pitfall | A pitfall committed or avoided on a profile topic |
83
+ | ③ | Profile pattern | An interesting pattern on a profile topic worth formalising |
84
+ | ④ | Off-profile pitfall | A pitfall on a topic prominent in the task but absent from the profile |
85
+ | ⑤ | Knowledge gap | A profile topic with confidence < 5 |
86
+ | ⑥ | Deep-dive | A profile topic at confidence 4–6, not yet mastered |
87
+
88
+ First match wins. No match → silent.
89
+
90
+ ---
91
+
92
+ ## Depth calibration
93
+
94
+ The lesson level is determined by the confidence score for the **specific topic being taught**,
95
+ adjusted by observations in the coaching notebook.
96
+
97
+ | Confidence | Level | Lesson angle |
98
+ |---|---|---|
99
+ | 0 – 3 | Junior | Introduce correct practice, explain from scratch, use analogies |
100
+ | 4 – 6 | Mid | Explain the why, mention trade-offs and alternatives |
101
+ | 7 – 9 | Senior | Edge cases, historical context, architectural implications |
102
+ | 10 | Cutting-edge | Latest developments — ignores level floor and taught-topics filter |
@@ -8,12 +8,35 @@ 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
+ flowchart TD
13
+ A([Task completed]) --> B[Check rate limit]
14
+ B -->|denied| Z([Silent])
15
+ B -->|allowed| D
16
+
17
+ subgraph loop["coaching loop"]
18
+ D[Select topic & depth]
19
+ E[Compose & deliver]
20
+ G[log_lesson]
21
+ end
22
+
23
+ D -->|nothing| Z
24
+ D -->|found| E
25
+ E --> G
26
+ G --> F([Done])
27
+ G -.->|prompts| U(["You: ✅ ❌ ⏭"])
28
+
29
+ style loop fill:none,stroke:#AAAAAA,stroke-dasharray:5 5,color:#757575
30
+ classDef action fill:#D4E4D8,stroke:#8BAF96,color:#1E1E1E
31
+ classDef term fill:#E8E8E4,stroke:#AAAAAA,color:#1E1E1E
32
+ classDef user fill:#F5EDE3,stroke:#D4A27F,color:#1E1E1E
33
+
34
+ class B,D,E,G action
35
+ class A,F,Z term
36
+ class U user
37
+ ```
38
+
39
+ → [Full decision flow: session startup · lesson selection · depth calibration](how-it-works.md)
17
40
 
18
41
  ---
19
42
 
@@ -43,6 +66,14 @@ Everything runs **locally**. No data leaves your machine. One SQLite file at `~/
43
66
  === "Light"
44
67
  ![Settings – light theme](screenshots/settings-light.png)
45
68
 
69
+ ### Lesson detail
70
+
71
+ === "Dark"
72
+ ![Lesson detail – dark theme](screenshots/lesson-redis-cache-stampede-dark.png)
73
+
74
+ === "Light"
75
+ ![Lesson detail – light theme](screenshots/lesson-redis-cache-stampede-light.png)
76
+
46
77
  ---
47
78
 
48
79
  ## Quick install