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.
- devcoach-0.3.10/.github/scripts/export_backup.py +138 -0
- devcoach-0.3.10/.github/scripts/fixtures/devcoach-backup.zip +0 -0
- {devcoach-0.3.8 → devcoach-0.3.10}/.github/scripts/take_screenshots.py +18 -19
- {devcoach-0.3.8 → devcoach-0.3.10}/.github/workflows/ci.yml +1 -1
- {devcoach-0.3.8 → devcoach-0.3.10}/.github/workflows/ruff-autofix.yml +9 -14
- {devcoach-0.3.8 → devcoach-0.3.10}/.gitignore +2 -0
- {devcoach-0.3.8 → devcoach-0.3.10}/PKG-INFO +30 -7
- {devcoach-0.3.8 → devcoach-0.3.10}/README.md +29 -6
- devcoach-0.3.10/docs/how-it-works.md +102 -0
- {devcoach-0.3.8 → devcoach-0.3.10}/docs/index.md +37 -6
- devcoach-0.3.10/docs/screenshots/knowledge-map-dark.png +0 -0
- devcoach-0.3.10/docs/screenshots/knowledge-map-light.png +0 -0
- devcoach-0.3.10/docs/screenshots/lesson-ci-cd-pipeline-stages-dark.png +0 -0
- devcoach-0.3.10/docs/screenshots/lesson-ci-cd-pipeline-stages-light.png +0 -0
- devcoach-0.3.10/docs/screenshots/lesson-docker-layer-cache-dark.png +0 -0
- devcoach-0.3.10/docs/screenshots/lesson-docker-layer-cache-light.png +0 -0
- devcoach-0.3.10/docs/screenshots/lesson-git-interactive-rebase-dark.png +0 -0
- devcoach-0.3.10/docs/screenshots/lesson-git-interactive-rebase-light.png +0 -0
- devcoach-0.3.10/docs/screenshots/lesson-postgresql-explain-analyze-dark.png +0 -0
- devcoach-0.3.10/docs/screenshots/lesson-postgresql-explain-analyze-light.png +0 -0
- devcoach-0.3.10/docs/screenshots/lesson-redis-cache-stampede-dark.png +0 -0
- devcoach-0.3.10/docs/screenshots/lesson-redis-cache-stampede-light.png +0 -0
- devcoach-0.3.10/docs/screenshots/lessons-dark.png +0 -0
- devcoach-0.3.10/docs/screenshots/lessons-light.png +0 -0
- devcoach-0.3.10/docs/screenshots/settings-dark.png +0 -0
- devcoach-0.3.10/docs/screenshots/settings-light.png +0 -0
- devcoach-0.3.10/docs/web-ui.md +144 -0
- {devcoach-0.3.8 → devcoach-0.3.10}/mkdocs.yml +7 -1
- {devcoach-0.3.8 → devcoach-0.3.10}/pyproject.toml +1 -1
- {devcoach-0.3.8 → devcoach-0.3.10}/sonar-project.properties +1 -1
- devcoach-0.3.10/src/devcoach/SKILL.md +490 -0
- {devcoach-0.3.8 → devcoach-0.3.10}/src/devcoach/cli/commands.py +15 -4
- {devcoach-0.3.8 → devcoach-0.3.10}/src/devcoach/core/coach.py +9 -10
- {devcoach-0.3.8 → devcoach-0.3.10}/src/devcoach/core/db.py +49 -20
- {devcoach-0.3.8 → devcoach-0.3.10}/src/devcoach/core/git.py +9 -11
- {devcoach-0.3.8 → devcoach-0.3.10}/src/devcoach/core/models.py +3 -43
- {devcoach-0.3.8 → devcoach-0.3.10}/src/devcoach/mcp/server.py +221 -113
- {devcoach-0.3.8 → devcoach-0.3.10}/src/devcoach/web/static/style.css +1 -0
- {devcoach-0.3.8 → devcoach-0.3.10}/src/devcoach/web/templates/lesson_detail.html +11 -5
- {devcoach-0.3.8 → devcoach-0.3.10}/tests/test_cli.py +1 -1
- {devcoach-0.3.8 → devcoach-0.3.10}/tests/test_mcp_server.py +276 -80
- {devcoach-0.3.8 → devcoach-0.3.10}/uv.lock +77 -77
- devcoach-0.3.8/.github/scripts/fixtures/devcoach-backup.zip +0 -0
- devcoach-0.3.8/docs/screenshots/knowledge-map-dark.png +0 -0
- devcoach-0.3.8/docs/screenshots/knowledge-map-light.png +0 -0
- devcoach-0.3.8/docs/screenshots/lessons-dark.png +0 -0
- devcoach-0.3.8/docs/screenshots/lessons-light.png +0 -0
- devcoach-0.3.8/docs/screenshots/settings-dark.png +0 -0
- devcoach-0.3.8/docs/screenshots/settings-light.png +0 -0
- devcoach-0.3.8/docs/web-ui.md +0 -88
- devcoach-0.3.8/src/devcoach/SKILL.md +0 -285
- {devcoach-0.3.8 → devcoach-0.3.10}/.github/dependabot.yml +0 -0
- {devcoach-0.3.8 → devcoach-0.3.10}/.github/workflows/update-screenshots.yml +0 -0
- {devcoach-0.3.8 → devcoach-0.3.10}/CLAUDE.md +0 -0
- {devcoach-0.3.8 → devcoach-0.3.10}/LICENSE +0 -0
- {devcoach-0.3.8 → devcoach-0.3.10}/NOTICE +0 -0
- {devcoach-0.3.8 → devcoach-0.3.10}/SKILL.md +0 -0
- {devcoach-0.3.8 → devcoach-0.3.10}/docs/PLAN.md +0 -0
- {devcoach-0.3.8 → devcoach-0.3.10}/docs/cli.md +0 -0
- {devcoach-0.3.8 → devcoach-0.3.10}/docs/configuration.md +0 -0
- {devcoach-0.3.8 → devcoach-0.3.10}/docs/favicon.svg +0 -0
- {devcoach-0.3.8 → devcoach-0.3.10}/docs/getting-started.md +0 -0
- {devcoach-0.3.8 → devcoach-0.3.10}/docs/mcp-server.md +0 -0
- {devcoach-0.3.8 → devcoach-0.3.10}/src/devcoach/__init__.py +0 -0
- {devcoach-0.3.8 → devcoach-0.3.10}/src/devcoach/cli/__init__.py +0 -0
- {devcoach-0.3.8 → devcoach-0.3.10}/src/devcoach/core/__init__.py +0 -0
- {devcoach-0.3.8 → devcoach-0.3.10}/src/devcoach/core/detect.py +0 -0
- {devcoach-0.3.8 → devcoach-0.3.10}/src/devcoach/core/prompts.py +0 -0
- {devcoach-0.3.8 → devcoach-0.3.10}/src/devcoach/mcp/__init__.py +0 -0
- {devcoach-0.3.8 → devcoach-0.3.10}/src/devcoach/web/__init__.py +0 -0
- {devcoach-0.3.8 → devcoach-0.3.10}/src/devcoach/web/app.py +0 -0
- {devcoach-0.3.8 → devcoach-0.3.10}/src/devcoach/web/static/favicon.svg +0 -0
- {devcoach-0.3.8 → devcoach-0.3.10}/src/devcoach/web/static/relative-time.js +0 -0
- {devcoach-0.3.8 → devcoach-0.3.10}/src/devcoach/web/static/vendor/alpinejs.min.js +0 -0
- {devcoach-0.3.8 → devcoach-0.3.10}/src/devcoach/web/static/vendor/flatpickr-dark.min.css +0 -0
- {devcoach-0.3.8 → devcoach-0.3.10}/src/devcoach/web/static/vendor/flatpickr.min.css +0 -0
- {devcoach-0.3.8 → devcoach-0.3.10}/src/devcoach/web/static/vendor/flatpickr.min.js +0 -0
- {devcoach-0.3.8 → devcoach-0.3.10}/src/devcoach/web/static/vendor/highlight.min.js +0 -0
- {devcoach-0.3.8 → devcoach-0.3.10}/src/devcoach/web/static/vendor/hljs-dark.min.css +0 -0
- {devcoach-0.3.8 → devcoach-0.3.10}/src/devcoach/web/static/vendor/hljs-light.min.css +0 -0
- {devcoach-0.3.8 → devcoach-0.3.10}/src/devcoach/web/static/vendor/htmx.min.js +0 -0
- {devcoach-0.3.8 → devcoach-0.3.10}/src/devcoach/web/static/vendor/icons/bitbucket.svg +0 -0
- {devcoach-0.3.8 → devcoach-0.3.10}/src/devcoach/web/static/vendor/icons/github.svg +0 -0
- {devcoach-0.3.8 → devcoach-0.3.10}/src/devcoach/web/static/vendor/icons/gitlab.svg +0 -0
- {devcoach-0.3.8 → devcoach-0.3.10}/src/devcoach/web/static/vendor/icons/vscode.svg +0 -0
- {devcoach-0.3.8 → devcoach-0.3.10}/src/devcoach/web/static/vendor/marked.min.js +0 -0
- {devcoach-0.3.8 → devcoach-0.3.10}/src/devcoach/web/static/vendor/tailwind.js +0 -0
- {devcoach-0.3.8 → devcoach-0.3.10}/src/devcoach/web/templates/base.html +0 -0
- {devcoach-0.3.8 → devcoach-0.3.10}/src/devcoach/web/templates/lessons.html +0 -0
- {devcoach-0.3.8 → devcoach-0.3.10}/src/devcoach/web/templates/profile.html +0 -0
- {devcoach-0.3.8 → devcoach-0.3.10}/src/devcoach/web/templates/settings.html +0 -0
- {devcoach-0.3.8 → devcoach-0.3.10}/tests/__init__.py +0 -0
- {devcoach-0.3.8 → devcoach-0.3.10}/tests/conftest.py +0 -0
- {devcoach-0.3.8 → devcoach-0.3.10}/tests/test_cli_commands.py +0 -0
- {devcoach-0.3.8 → devcoach-0.3.10}/tests/test_coach.py +0 -0
- {devcoach-0.3.8 → devcoach-0.3.10}/tests/test_db_extra.py +0 -0
- {devcoach-0.3.8 → devcoach-0.3.10}/tests/test_detect.py +0 -0
- {devcoach-0.3.8 → devcoach-0.3.10}/tests/test_git.py +0 -0
- {devcoach-0.3.8 → devcoach-0.3.10}/tests/test_prompts.py +0 -0
- {devcoach-0.3.8 → devcoach-0.3.10}/tests/test_web.py +0 -0
- {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()
|
|
Binary file
|
|
@@ -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
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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@
|
|
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:
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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.
|
|
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
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|

|
|
45
68
|
|
|
69
|
+
### Lesson detail
|
|
70
|
+
|
|
71
|
+
=== "Dark"
|
|
72
|
+

|
|
73
|
+
|
|
74
|
+
=== "Light"
|
|
75
|
+

|
|
76
|
+
|
|
46
77
|
---
|
|
47
78
|
|
|
48
79
|
## Quick install
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|