devcoach 0.3.9__tar.gz → 0.3.11__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 (93) hide show
  1. {devcoach-0.3.9 → devcoach-0.3.11}/.gitignore +2 -0
  2. {devcoach-0.3.9 → devcoach-0.3.11}/CLAUDE.md +1 -1
  3. {devcoach-0.3.9 → devcoach-0.3.11}/PKG-INFO +21 -7
  4. {devcoach-0.3.9 → devcoach-0.3.11}/README.md +20 -6
  5. devcoach-0.3.11/docs/how-it-works.md +86 -0
  6. {devcoach-0.3.9 → devcoach-0.3.11}/docs/index.md +17 -21
  7. {devcoach-0.3.9 → devcoach-0.3.11}/pyproject.toml +1 -1
  8. {devcoach-0.3.9 → devcoach-0.3.11}/sonar-project.properties +1 -1
  9. {devcoach-0.3.9 → devcoach-0.3.11}/src/devcoach/SKILL.md +144 -48
  10. {devcoach-0.3.9 → devcoach-0.3.11}/src/devcoach/cli/commands.py +5 -1
  11. {devcoach-0.3.9 → devcoach-0.3.11}/src/devcoach/core/coach.py +5 -1
  12. {devcoach-0.3.9 → devcoach-0.3.11}/src/devcoach/core/db.py +30 -62
  13. {devcoach-0.3.9 → devcoach-0.3.11}/src/devcoach/core/models.py +11 -9
  14. {devcoach-0.3.9 → devcoach-0.3.11}/src/devcoach/mcp/server.py +96 -27
  15. {devcoach-0.3.9 → devcoach-0.3.11}/src/devcoach/web/app.py +7 -1
  16. {devcoach-0.3.9 → devcoach-0.3.11}/src/devcoach/web/templates/settings.html +1 -0
  17. {devcoach-0.3.9 → devcoach-0.3.11}/tests/conftest.py +5 -3
  18. {devcoach-0.3.9 → devcoach-0.3.11}/tests/test_cli_commands.py +26 -1
  19. {devcoach-0.3.9 → devcoach-0.3.11}/tests/test_db_extra.py +10 -9
  20. {devcoach-0.3.9 → devcoach-0.3.11}/tests/test_mcp_server.py +285 -3
  21. {devcoach-0.3.9 → devcoach-0.3.11}/tests/test_web_extra.py +14 -0
  22. {devcoach-0.3.9 → devcoach-0.3.11}/uv.lock +4 -4
  23. devcoach-0.3.9/SKILL.md +0 -185
  24. devcoach-0.3.9/docs/how-it-works.md +0 -112
  25. {devcoach-0.3.9 → devcoach-0.3.11}/.github/dependabot.yml +0 -0
  26. {devcoach-0.3.9 → devcoach-0.3.11}/.github/scripts/export_backup.py +0 -0
  27. {devcoach-0.3.9 → devcoach-0.3.11}/.github/scripts/fixtures/devcoach-backup.zip +0 -0
  28. {devcoach-0.3.9 → devcoach-0.3.11}/.github/scripts/take_screenshots.py +0 -0
  29. {devcoach-0.3.9 → devcoach-0.3.11}/.github/workflows/ci.yml +0 -0
  30. {devcoach-0.3.9 → devcoach-0.3.11}/.github/workflows/ruff-autofix.yml +0 -0
  31. {devcoach-0.3.9 → devcoach-0.3.11}/.github/workflows/update-screenshots.yml +0 -0
  32. {devcoach-0.3.9 → devcoach-0.3.11}/LICENSE +0 -0
  33. {devcoach-0.3.9 → devcoach-0.3.11}/NOTICE +0 -0
  34. {devcoach-0.3.9 → devcoach-0.3.11}/docs/PLAN.md +0 -0
  35. {devcoach-0.3.9 → devcoach-0.3.11}/docs/cli.md +0 -0
  36. {devcoach-0.3.9 → devcoach-0.3.11}/docs/configuration.md +0 -0
  37. {devcoach-0.3.9 → devcoach-0.3.11}/docs/favicon.svg +0 -0
  38. {devcoach-0.3.9 → devcoach-0.3.11}/docs/getting-started.md +0 -0
  39. {devcoach-0.3.9 → devcoach-0.3.11}/docs/mcp-server.md +0 -0
  40. {devcoach-0.3.9 → devcoach-0.3.11}/docs/screenshots/knowledge-map-dark.png +0 -0
  41. {devcoach-0.3.9 → devcoach-0.3.11}/docs/screenshots/knowledge-map-light.png +0 -0
  42. {devcoach-0.3.9 → devcoach-0.3.11}/docs/screenshots/lesson-ci-cd-pipeline-stages-dark.png +0 -0
  43. {devcoach-0.3.9 → devcoach-0.3.11}/docs/screenshots/lesson-ci-cd-pipeline-stages-light.png +0 -0
  44. {devcoach-0.3.9 → devcoach-0.3.11}/docs/screenshots/lesson-docker-layer-cache-dark.png +0 -0
  45. {devcoach-0.3.9 → devcoach-0.3.11}/docs/screenshots/lesson-docker-layer-cache-light.png +0 -0
  46. {devcoach-0.3.9 → devcoach-0.3.11}/docs/screenshots/lesson-git-interactive-rebase-dark.png +0 -0
  47. {devcoach-0.3.9 → devcoach-0.3.11}/docs/screenshots/lesson-git-interactive-rebase-light.png +0 -0
  48. {devcoach-0.3.9 → devcoach-0.3.11}/docs/screenshots/lesson-postgresql-explain-analyze-dark.png +0 -0
  49. {devcoach-0.3.9 → devcoach-0.3.11}/docs/screenshots/lesson-postgresql-explain-analyze-light.png +0 -0
  50. {devcoach-0.3.9 → devcoach-0.3.11}/docs/screenshots/lesson-redis-cache-stampede-dark.png +0 -0
  51. {devcoach-0.3.9 → devcoach-0.3.11}/docs/screenshots/lesson-redis-cache-stampede-light.png +0 -0
  52. {devcoach-0.3.9 → devcoach-0.3.11}/docs/screenshots/lessons-dark.png +0 -0
  53. {devcoach-0.3.9 → devcoach-0.3.11}/docs/screenshots/lessons-light.png +0 -0
  54. {devcoach-0.3.9 → devcoach-0.3.11}/docs/screenshots/settings-dark.png +0 -0
  55. {devcoach-0.3.9 → devcoach-0.3.11}/docs/screenshots/settings-light.png +0 -0
  56. {devcoach-0.3.9 → devcoach-0.3.11}/docs/web-ui.md +0 -0
  57. {devcoach-0.3.9 → devcoach-0.3.11}/mkdocs.yml +0 -0
  58. {devcoach-0.3.9 → devcoach-0.3.11}/src/devcoach/__init__.py +0 -0
  59. {devcoach-0.3.9 → devcoach-0.3.11}/src/devcoach/cli/__init__.py +0 -0
  60. {devcoach-0.3.9 → devcoach-0.3.11}/src/devcoach/core/__init__.py +0 -0
  61. {devcoach-0.3.9 → devcoach-0.3.11}/src/devcoach/core/detect.py +0 -0
  62. {devcoach-0.3.9 → devcoach-0.3.11}/src/devcoach/core/git.py +0 -0
  63. {devcoach-0.3.9 → devcoach-0.3.11}/src/devcoach/core/prompts.py +0 -0
  64. {devcoach-0.3.9 → devcoach-0.3.11}/src/devcoach/mcp/__init__.py +0 -0
  65. {devcoach-0.3.9 → devcoach-0.3.11}/src/devcoach/web/__init__.py +0 -0
  66. {devcoach-0.3.9 → devcoach-0.3.11}/src/devcoach/web/static/favicon.svg +0 -0
  67. {devcoach-0.3.9 → devcoach-0.3.11}/src/devcoach/web/static/relative-time.js +0 -0
  68. {devcoach-0.3.9 → devcoach-0.3.11}/src/devcoach/web/static/style.css +0 -0
  69. {devcoach-0.3.9 → devcoach-0.3.11}/src/devcoach/web/static/vendor/alpinejs.min.js +0 -0
  70. {devcoach-0.3.9 → devcoach-0.3.11}/src/devcoach/web/static/vendor/flatpickr-dark.min.css +0 -0
  71. {devcoach-0.3.9 → devcoach-0.3.11}/src/devcoach/web/static/vendor/flatpickr.min.css +0 -0
  72. {devcoach-0.3.9 → devcoach-0.3.11}/src/devcoach/web/static/vendor/flatpickr.min.js +0 -0
  73. {devcoach-0.3.9 → devcoach-0.3.11}/src/devcoach/web/static/vendor/highlight.min.js +0 -0
  74. {devcoach-0.3.9 → devcoach-0.3.11}/src/devcoach/web/static/vendor/hljs-dark.min.css +0 -0
  75. {devcoach-0.3.9 → devcoach-0.3.11}/src/devcoach/web/static/vendor/hljs-light.min.css +0 -0
  76. {devcoach-0.3.9 → devcoach-0.3.11}/src/devcoach/web/static/vendor/htmx.min.js +0 -0
  77. {devcoach-0.3.9 → devcoach-0.3.11}/src/devcoach/web/static/vendor/icons/bitbucket.svg +0 -0
  78. {devcoach-0.3.9 → devcoach-0.3.11}/src/devcoach/web/static/vendor/icons/github.svg +0 -0
  79. {devcoach-0.3.9 → devcoach-0.3.11}/src/devcoach/web/static/vendor/icons/gitlab.svg +0 -0
  80. {devcoach-0.3.9 → devcoach-0.3.11}/src/devcoach/web/static/vendor/icons/vscode.svg +0 -0
  81. {devcoach-0.3.9 → devcoach-0.3.11}/src/devcoach/web/static/vendor/marked.min.js +0 -0
  82. {devcoach-0.3.9 → devcoach-0.3.11}/src/devcoach/web/static/vendor/tailwind.js +0 -0
  83. {devcoach-0.3.9 → devcoach-0.3.11}/src/devcoach/web/templates/base.html +0 -0
  84. {devcoach-0.3.9 → devcoach-0.3.11}/src/devcoach/web/templates/lesson_detail.html +0 -0
  85. {devcoach-0.3.9 → devcoach-0.3.11}/src/devcoach/web/templates/lessons.html +0 -0
  86. {devcoach-0.3.9 → devcoach-0.3.11}/src/devcoach/web/templates/profile.html +0 -0
  87. {devcoach-0.3.9 → devcoach-0.3.11}/tests/__init__.py +0 -0
  88. {devcoach-0.3.9 → devcoach-0.3.11}/tests/test_cli.py +0 -0
  89. {devcoach-0.3.9 → devcoach-0.3.11}/tests/test_coach.py +0 -0
  90. {devcoach-0.3.9 → devcoach-0.3.11}/tests/test_detect.py +0 -0
  91. {devcoach-0.3.9 → devcoach-0.3.11}/tests/test_git.py +0 -0
  92. {devcoach-0.3.9 → devcoach-0.3.11}/tests/test_prompts.py +0 -0
  93. {devcoach-0.3.9 → devcoach-0.3.11}/tests/test_web.py +0 -0
@@ -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
@@ -182,7 +182,7 @@ The `log_lesson` tool accepts this schema:
182
182
  {
183
183
  "id": "uuid-or-random-string",
184
184
  "timestamp": "2025-01-15T20:30:00Z",
185
- "topic_id": "python_generators",
185
+ "topic_id": "python",
186
186
  "category": "python",
187
187
  "title": "Generator expressions vs list comprehensions",
188
188
  "level": "mid",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devcoach
3
- Version: 0.3.9
3
+ Version: 0.3.11
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,26 @@ 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
+
267
+ → [Full decision flow: session startup · lesson selection · depth calibration](https://ultimaphoenix.github.io/dev-coach/how-it-works/)
254
268
 
255
269
  Everything runs **locally**. No data leaves your machine. One SQLite file at `~/.devcoach/coaching.db`.
256
270
 
@@ -14,12 +14,26 @@
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
+
36
+ → [Full decision flow: session startup · lesson selection · depth calibration](https://ultimaphoenix.github.io/dev-coach/how-it-works/)
23
37
 
24
38
  Everything runs **locally**. No data leaves your machine. One SQLite file at `~/.devcoach/coaching.db`.
25
39
 
@@ -0,0 +1,86 @@
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
+
30
+ ---
31
+
32
+ ## Coaching loop
33
+
34
+ After every technical task Claude evaluates whether to deliver a lesson.
35
+ The loop is silent when nothing is worth teaching or when the rate limit is reached.
36
+
37
+ ```mermaid
38
+ flowchart TD
39
+ A([Task completed]) --> B[Check rate limit]
40
+ B -->|denied| Z([Silent])
41
+ B -->|allowed| D
42
+
43
+ subgraph loop["coaching loop"]
44
+ D[Select topic & depth]
45
+ E[Compose & deliver]
46
+ G[log_lesson]
47
+ end
48
+
49
+ D -->|nothing| Z
50
+ D -->|found| E
51
+ E --> G
52
+ G --> F([Done])
53
+ G -.->|prompts| U(["You: ✅ ❌ ⏭"])
54
+ ```
55
+
56
+ ---
57
+
58
+ ## Lesson selection
59
+
60
+ When a teachable concept is found, devcoach walks this priority list from top to bottom
61
+ and picks the first match. Depth is then calibrated to the per-topic confidence score.
62
+
63
+ | Priority | Trigger | Condition |
64
+ |:---:|---|---|
65
+ | ① | Notebook follow-up | The coaching notebook flagged an angle relevant to the current task |
66
+ | ② | Profile pitfall | A pitfall committed or avoided on a profile topic |
67
+ | ③ | Profile pattern | An interesting pattern on a profile topic worth formalising |
68
+ | ④ | Off-profile pitfall | A pitfall on a topic prominent in the task but absent from the profile |
69
+ | ⑤ | Knowledge gap | A profile topic with confidence < 5 |
70
+ | ⑥ | Deep-dive | A profile topic at confidence 4–6, not yet mastered |
71
+
72
+ First match wins. No match → silent.
73
+
74
+ ---
75
+
76
+ ## Depth calibration
77
+
78
+ The lesson level is determined by the confidence score for the **specific topic being taught**,
79
+ adjusted by observations in the coaching notebook.
80
+
81
+ | Confidence | Level | Lesson angle |
82
+ |---|---|---|
83
+ | 0 – 3 | Junior | Introduce correct practice, explain from scratch, use analogies |
84
+ | 4 – 6 | Mid | Explain the why, mention trade-offs and alternatives |
85
+ | 7 – 9 | Senior | Edge cases, historical context, architectural implications |
86
+ | 10 | Cutting-edge | Latest developments — ignores level floor and taught-topics filter |
@@ -9,29 +9,25 @@ Everything runs **locally**. No data leaves your machine. One SQLite file at `~/
9
9
  ## How it works
10
10
 
11
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
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: ✅ ❌ ⏭"])
32
28
  ```
33
29
 
34
- See [How it works](how-it-works.md) for the full decision flow.
30
+ [Full decision flow: session startup · lesson selection · depth calibration](how-it-works.md)
35
31
 
36
32
  ---
37
33
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "devcoach"
3
- version = "0.3.9"
3
+ version = "0.3.11"
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.9
5
+ sonar.projectVersion=0.3.11
6
6
 
7
7
  sonar.sources=src
8
8
  sonar.tests=tests
@@ -9,7 +9,10 @@ description: >
9
9
  lesson at the bottom of the response. DO NOT wait for the user to ask explicitly:
10
10
  activate autonomously. Also use this skill when the user asks "what did I learn
11
11
  today/this week", "show me my profile", "how good am I at X", "coaching log",
12
- "devcoach".
12
+ "devcoach", "setup devcoach", "redo onboarding", "configure my profile",
13
+ "reset my topics", or any similar request to (re-)initialise the knowledge profile.
14
+ No session startup required: onboarding status is checked before every lesson
15
+ and is always accurate regardless of context compaction or plan mode.
13
16
  ---
14
17
 
15
18
  # devcoach — Progressive Coaching
@@ -19,28 +22,44 @@ by teaching one thing at a time, at the right moment, based on what they actuall
19
22
 
20
23
  ---
21
24
 
22
- ## Session startup
25
+ ## Onboarding flow
23
26
 
24
- At the start of every devcoach session, read the MCP resource `devcoach://onboarding`.
27
+ The onboarding flow runs inline, immediately before delivering a lesson, whenever
28
+ it is needed. There is no separate session startup phase — this makes the skill
29
+ robust to context compaction and plan-mode transitions.
25
30
 
26
- If `needs_onboarding` is **true**, run the onboarding flow **before anything else**:
31
+ Check `knowledge_ready` and `notebook_ready` from `devcoach://onboarding` independently
32
+ — each step can run alone:
33
+
34
+ ```
35
+ knowledge_ready = false → run Steps 1–3 (topic collection + complete_onboarding)
36
+ notebook_ready = false → run Step 4 (notebook initialisation)
37
+ both ready → proceed normally
38
+ ```
39
+
40
+ This means a user who restores from backup has their knowledge automatically, so
41
+ only Step 4 runs. On-demand re-setup ("redo onboarding", "reset my topics") always
42
+ re-runs Steps 1–3 regardless of `knowledge_ready`.
27
43
 
28
44
  ### Step 1 — Offer to restore from backup
29
45
  Ask once: *"Do you have an existing devcoach backup to restore? If yes, provide the
30
46
  file path — otherwise I'll help you build your profile from scratch."*
31
47
 
32
- If a path is provided: call `restore` (CLI) with the file. When complete, call the
33
- MCP tool `complete_onboarding` with empty maps to mark setup as done
34
- (the restored profile is already in the DB). Skip the remaining steps.
48
+ If a path is provided: call `restore` (CLI) with the file. The restore process
49
+ brings back knowledge entries automatically no further DB steps needed. Re-read
50
+ `devcoach://onboarding` after restore; if `knowledge_ready` is now true, skip to
51
+ Step 4. If `notebook_ready` is also true, proceed normally.
35
52
 
36
53
  ### Step 2 — Choose setup mode
37
54
  Ask: *"Would you like me to detect your tech stack automatically from this project,
38
55
  or set it up manually through a conversation?"*
39
56
 
40
57
  **Automatic mode:**
41
- - Read `detected_stack` from `devcoach://onboarding` and present those topics in a
42
- clear list with their suggested confidence scores.
43
- - For each, ask the user to confirm or adjust: *"Looks right? Or enter 1–10."*
58
+ - Read `devcoach://onboarding` and present a merged topic list: `detected_stack`
59
+ (auto-detected from project files) enriched with relevant entries from
60
+ `default_topics` (the project's default knowledge map).
61
+ - Show each topic with its suggested confidence. Ask the user to confirm, adjust,
62
+ or remove each: *"Looks right? Or enter 1–10 to change it."*
44
63
  - After the list, ask: *"Anything else I missed? List any tools, languages,
45
64
  frameworks, or practices you work with regularly."* — add each with a confidence.
46
65
 
@@ -48,9 +67,9 @@ or set it up manually through a conversation?"*
48
67
  - Have a free-form conversation: *"Tell me about the technologies you work with
49
68
  day-to-day. For each one I'll ask how confident you are:
50
69
  1–3 = still learning · 4–6 = comfortable · 7–9 = strong · 10 = expert."*
51
- - Probe across domains: programming languages, frameworks, databases, infrastructure,
52
- version control practices, branching strategies, CI/CD pipelines, testing,
53
- architecture patterns, etc. Keep probing until the user says they're done.
70
+ - Use `default_topics` as a domain checklist: probe areas the user hasn't mentioned
71
+ (languages, frameworks, databases, infrastructure, version control, CI/CD, testing,
72
+ architecture patterns, etc.). Keep probing until the user says they're done.
54
73
 
55
74
  ### Step 3 — Propose groups and save
56
75
  Once the full topic list is agreed:
@@ -66,18 +85,44 @@ Once the full topic list is agreed:
66
85
  "groups": { "Languages": ["lang_a"], "DevOps": ["tool_b"], "Version Control": ["practice_c"] }
67
86
  }
68
87
  ```
69
- - Confirm setup is complete and continue normally.
88
+ - Confirm setup is complete and continue to Step 4.
70
89
 
71
90
  **Rule:** Never ask about groups during topic collection. Propose them only in
72
91
  Step 3 after all topics are known.
73
92
 
93
+ ### Step 4 — Initialise the coaching notebook
94
+
95
+ Write `~/.devcoach/learning-state.md` with observations from this conversation.
96
+ If the file already exists (returning user or post-restore), integrate new
97
+ observations without overwriting prior entries.
98
+
99
+ ```markdown
100
+ # devcoach — Coaching Notebook
101
+ _Last updated: [ISO timestamp]_
102
+
103
+ ## Observations
104
+ [User background, confidence style, or gaps noted during onboarding.
105
+ Leave empty if nothing notable was observed.]
106
+
107
+ ## Recurring patterns
108
+ [Leave empty — nothing observed yet.]
109
+
110
+ ## Recommended focus
111
+ [Topics the user flagged as priorities or areas of uncertainty.]
112
+
113
+ ## Open hypotheses
114
+ [Leave empty — nothing to track yet.]
115
+ ```
116
+
74
117
  ---
75
118
 
76
119
  ## Session context
77
120
 
78
- After the onboarding check, read the file `~/.devcoach/learning-state.md`.
121
+ Before the first lesson in this context window, read `~/.devcoach/learning-state.md`
122
+ if it exists and has not already been loaded. Use its content for all subsequent
123
+ lessons in this window — do not re-read it on every lesson.
79
124
 
80
- If it exists and is non-empty, load it as your coaching context for this session:
125
+ If it exists and is non-empty, load it as your coaching context:
81
126
  - Resume patterns and hypotheses you noted in previous sessions
82
127
  - Prioritise angles you flagged as still pending
83
128
  - Avoid re-covering ground you noted as absorbed
@@ -93,6 +138,8 @@ Do not mention this file to the user. It is an internal coaching tool.
93
138
 
94
139
  Always read these MCP resources before deciding to teach:
95
140
 
141
+ - `devcoach://onboarding` — check `knowledge_ready` and `notebook_ready`; if either
142
+ is false, run the onboarding flow (see above) before delivering the lesson
96
143
  - `devcoach://rate-limit` — check `allowed`; if false, skip entirely
97
144
  - `devcoach://taught-topics` — never repeat a topic already in this list
98
145
  - `devcoach://profile` — use confidence scores to pick depth, and treat each topic
@@ -103,6 +150,10 @@ Always read these MCP resources before deciding to teach:
103
150
  - **Open hypotheses** relevant to the current task → watch for confirming/refuting evidence
104
151
  - **Recurring patterns** → use them to calibrate depth and angle, not just the confidence score
105
152
 
153
+ If `devcoach://profile` returns an **empty knowledge map**, do not deliver a lesson.
154
+ Run the onboarding flow first (Steps 1–3 of Session startup), then resume lesson
155
+ delivery once `complete_onboarding` has been called.
156
+
106
157
  ---
107
158
 
108
159
  ## Lesson levels
@@ -146,6 +197,14 @@ Evaluate whether to append a lesson **after every technical response** that invo
146
197
  - DB queries, optimisations, migrations
147
198
  - Security, performance, scalability
148
199
  - CLI, scripting, automation
200
+ - Completing a git commit — the commit is a natural unit-of-work checkpoint;
201
+ treat it as a **high-priority** lesson trigger
202
+
203
+ **Commit checkpoint:** When a successful `git commit` is the trigger, the staged
204
+ diff and commit message visible in the preceding Bash output are the primary
205
+ teaching context — they capture exactly what was written and why. Use the changed
206
+ code (not the commit mechanics themselves) as the basis for lesson selection,
207
+ exactly as you would for any other code task.
149
208
 
150
209
  **Do not activate** for: pure factual questions, web searches, translations, creative
151
210
  writing, non-technical conversation.
@@ -288,7 +347,7 @@ Call `log_lesson` right after delivering the lesson, without waiting for feedbac
288
347
  {
289
348
  "id": "unique-slug-or-uuid",
290
349
  "timestamp": "2026-04-27T14:30:00Z",
291
- "topic_id": "snake_case_identifier",
350
+ "topic_id": "topic",
292
351
  "categories": ["the_topic_category", "architecture"],
293
352
  "title": "Lesson title",
294
353
  "level": "junior|mid|senior",
@@ -298,45 +357,44 @@ Call `log_lesson` right after delivering the lesson, without waiting for feedbac
298
357
  }
299
358
  ```
300
359
 
360
+ **`topic_id` naming:** use the single most characterizing word for the domain
361
+ (e.g. `sqlite`, `python`, `docker`). For compound concepts with no single-word
362
+ equivalent, keep the essential part — `ci_cd_pipeline_design` → `ci_cd`. Never
363
+ more than 3 words.
364
+
301
365
  Git metadata (`project`, `repository`, `branch`, `commit_hash`, `folder`,
302
366
  `repository_platform`) is **auto-detected server-side**. Do not run git commands
303
367
  manually. Omitting these fields is correct and will not reduce lesson quality.
304
368
 
305
369
  `log_lesson` returns the saved `Lesson` object with all resolved fields.
306
370
 
307
- ### Step 1b — Collect feedback interactively
371
+ ### Step 1b — Feedback is collected by log_lesson
308
372
 
309
- **Claude Code (CLI / IDE):** `AskUserQuestion` is available in your tool list.
310
- Call it immediately after `log_lesson` returns, before the user sends another message:
373
+ `log_lesson` asks "Did that land?" via an interactive MCP elicitation prompt.
374
+ The user selects know / don't know / skip directly in the client UI no text
375
+ parsing required.
311
376
 
312
- ```
313
- question : "Did that land?"
314
- options : ["✅ know — got it", "❌ don't know — need to revisit", "⏭️ skip"]
315
- ```
377
+ Read the `feedback` field of the returned `Lesson` object:
316
378
 
317
- Wait for the reply, then go to Step 2 with the result.
318
- Do **not** append the text prompt in the lesson body `AskUserQuestion` replaces it.
379
+ - `"know"` or `"dont_know"` feedback was given and confidence was already
380
+ adjusted server-side. Go to Step 3 (starring). **Do not call `submit_feedback`.**
381
+ - `null` / absent → elicitation was declined or not supported by this client.
382
+ Fall back: append the text prompt below, capture the reply in the next message
383
+ turn, then call `submit_feedback` manually (Step 2).
319
384
 
320
- **Claude Desktop / claude.ai web:** `AskUserQuestion` is not in your tool list.
321
- After the lesson body, end the response with this exact block — the interface will
322
- render the options as clickable buttons:
385
+ **Fallback prompt (only when feedback is null):**
323
386
 
324
387
  Did that land?
325
388
  - ✅ know — got it
326
389
  - ❌ don't know — need to revisit
327
390
  - ⏭️ skip
328
391
 
329
- Keep labels short (≤ 5 words). Do not wrap in a blockquote or code fence — plain
330
- list format is what the client renders as buttons.
331
- When the user clicks or types a reply that matches feedback (know / don't know / skip,
332
- ✅, ❌, 1 / 2 / 3), treat it as feedback before handling any new request in that message.
333
-
334
392
  ---
335
393
 
336
- ### Step 2 — Record feedback and conditionally adjust confidence
394
+ ### Step 2 — Record feedback and conditionally adjust confidence (fallback only)
337
395
 
338
- When feedback is received (via `AskUserQuestion` reply or next-message fallback), call
339
- `submit_feedback` according to this table:
396
+ When `log_lesson` returned `feedback: null` (elicitation declined or not supported),
397
+ collect feedback via the fallback prompt and call `submit_feedback` according to this table:
340
398
 
341
399
  | User response | Condition | Action |
342
400
  |---|---|---|
@@ -466,10 +524,46 @@ When the user asks about their learning journey, use the MCP tools and resources
466
524
 
467
525
  ## 7. Dynamic calibration
468
526
 
469
- Every 10 lessons delivered, re-evaluate the profile:
470
- - If the user never showed gaps on X in 10 sessions → raise confidence via `update_knowledge`
471
- - If the user received 3+ lessons on the same topic → consider raising the level
472
- - If the user's questions on X become consistently more advanced → raise confidence by 2
527
+ Triggered after every `log_lesson` where `total_lessons % 10 == 0`.
528
+
529
+ **Step 1 Fetch the window**
530
+
531
+ Call `get_lessons()` (default `limit=10`, newest first) to get the last 10 lessons.
532
+ Read `devcoach://profile` for the current knowledge map.
533
+
534
+ **Step 2 — Per-topic signal analysis**
535
+
536
+ Group the 10 lessons by `topic_id`. For each topic that appeared:
537
+
538
+ | Signal | Condition | Action |
539
+ |---|---|---|
540
+ | Consistent mastery | All feedback `know`, no `dont_know` | `update_knowledge(topic, +1)` if confidence < 9 |
541
+ | Persistent gap | 2+ lessons on same topic, any `dont_know` | `update_knowledge(topic, -1)` if confidence > 1 |
542
+ | Recurring topic | 3+ lessons on same topic, mixed or no feedback | no confidence change — note in notebook |
543
+ | New topic | `topic_id` absent from `devcoach://profile` | `add_topic` — see Step 3 |
544
+
545
+ Apply at most **one** `update_knowledge` call per topic per calibration run.
546
+ Never call `update_knowledge` on a topic with confidence 10 (already mastered).
547
+
548
+ **Step 3 — New topic discovery**
549
+
550
+ For each lesson whose `topic_id` is not in `devcoach://profile`:
551
+ - 2+ lessons share this `topic_id` in the window → call `add_topic(topic_id, confidence=5)`;
552
+ assign it to the same group as the closest related existing topic, or `"Other"` if unclear.
553
+ - Only 1 lesson on this `topic_id` → note under **Open hypotheses** in the notebook; do not add yet.
554
+
555
+ **Step 4 — Update the coaching notebook**
556
+
557
+ Read `~/.devcoach/learning-state.md`. Merge findings into the relevant sections:
558
+
559
+ - **Recurring patterns** — append any `topic_id` that appeared 3+ times, with its count
560
+ and whether feedback was positive, negative, or absent.
561
+ - **Recommended focus** — replace or append topics that had 2+ `dont_know` in the window.
562
+ - **Open hypotheses** — add single-occurrence new topics not yet in the profile;
563
+ remove hypotheses confirmed (topic added) or disproved (not seen in 20+ subsequent lessons).
564
+
565
+ Always update `_Last updated` to the current ISO timestamp.
566
+ Never delete prior entries — integrate new observations alongside existing ones.
473
567
 
474
568
  ---
475
569
 
@@ -477,14 +571,16 @@ Every 10 lessons delivered, re-evaluate the profile:
477
571
 
478
572
  - **Never break the flow** of the main response — the lesson is always at the bottom
479
573
  - **Never mention** that you skipped a lesson due to rate limit
480
- - **Always read** `devcoach://rate-limit` before deciding to teach
481
- - **Always read** `devcoach://taught-topics` before selecting a lesson topic
482
- - **Always read** `devcoach://profile` to calibrate level and topic selection
574
+ - **Always read before deciding to teach:** `devcoach://onboarding`,
575
+ `devcoach://rate-limit`, `devcoach://taught-topics`, `devcoach://profile`
576
+ - Onboarding is checked before every lesson no separate session startup.
577
+ Works correctly after context compaction and after plan-mode transitions.
483
578
  - The lesson should feel **natural and contextual**, not a mechanical add-on
484
579
  - If there is nothing interesting to teach → stay silent. Better nothing than forced.
485
- - In Claude Code: use `AskUserQuestion` after `log_lesson` do not append the text prompt
486
- - In Claude Desktop / web: append the text prompt; capture reply in the next message turn
487
- - Never call `update_knowledge` directly after `log_lesson` — wait for feedback
488
- - `submit_feedback` handles the confidence delta; skip it entirely on no response
580
+ - Feedback is handled inside `log_lesson` via MCP elicitation only call
581
+ `submit_feedback` manually if `log_lesson` returned `feedback: null`
582
+ - Never call `update_knowledge` directly after `log_lesson` — feedback handles the delta
489
583
  - Propose starring when `don't know` on mid/senior, or when the topic recurs 2+ times
490
584
  - Never star a lesson silently — always ask first
585
+ - After each `log_lesson`, check `total_lessons` from `devcoach://rate-limit`;
586
+ when `total_lessons % 10 == 0`, run the dynamic calibration from section 7
@@ -369,10 +369,11 @@ def cmd_backup(args: argparse.Namespace) -> None:
369
369
 
370
370
  out_path = Path(args.output)
371
371
  out_path.write_bytes(data)
372
+ notebook_note = " + notebook" if db.LEARNING_STATE_PATH.exists() else ""
372
373
  console.print(
373
374
  f"[green]Backup saved:[/green] {out_path} "
374
375
  f"([cyan]{lessons_count}[/cyan] lessons, "
375
- f"[cyan]{knowledge_count}[/cyan] topics)"
376
+ f"[cyan]{knowledge_count}[/cyan] topics{notebook_note})"
376
377
  )
377
378
 
378
379
 
@@ -398,6 +399,8 @@ def cmd_restore(args: argparse.Namespace) -> None:
398
399
  if result["invalid"]:
399
400
  parts.append(f"[red]{result['invalid']}[/red] rejected (invalid)")
400
401
  console.print(f"[green]✓[/green] Lessons: {', '.join(parts)}")
402
+ if result["learning_state"]:
403
+ console.print("[green]✓[/green] Notebook restored")
401
404
 
402
405
 
403
406
  def _claude_desktop_config() -> Path:
@@ -543,6 +546,7 @@ def cmd_setup(_args: argparse.Namespace) -> None:
543
546
  db.set_setting(conn, "onboarding_completed", "1")
544
547
  console.print(
545
548
  f"[green]✓[/green] Restored: {result['topics']} topics, {result['lessons']} lessons"
549
+ + (", notebook" if result["learning_state"] else "")
546
550
  )
547
551
  console.print("[green]Setup complete![/green]")
548
552
  return
@@ -50,10 +50,14 @@ def check_rate_limit(conn: sqlite3.Connection) -> RateLimitResult:
50
50
  remaining = settings.min_gap_minutes - elapsed_minutes
51
51
  gap_h, gap_m = divmod(settings.min_gap_minutes, 60)
52
52
  rem_h, rem_m = divmod(int(remaining), 60)
53
+ if elapsed_minutes < 0:
54
+ ago_text = f"in {-elapsed_minutes:.0f}m (future timestamp)"
55
+ else:
56
+ ago_text = f"{elapsed_minutes:.0f}m ago"
53
57
  return RateLimitResult(
54
58
  allowed=False,
55
59
  reason=(
56
- f"Too soon: last lesson {elapsed_minutes:.0f}m ago, "
60
+ f"Too soon: last lesson {ago_text}, "
57
61
  f"minimum interval is {gap_h}h {gap_m}m "
58
62
  f"({rem_h}h {rem_m}m remaining)"
59
63
  ),