From 9c8635fc454c467d7143fb6b95bd87a769d83d34 Mon Sep 17 00:00:00 2001 From: Daniel Bejarano Date: Tue, 3 Mar 2026 14:04:03 -0600 Subject: [PATCH 1/2] feat: add debugging skill with error detection in hooks Add debugging category with error-reading, debugging-mindset, and common-errors concepts. Add quiz questions for each. Detect error patterns in command output (skip test runners). Closes DOJ-2434 Co-Authored-By: Claude Opus 4.6 --- data/concept-tree.json | 30 ++++++++++++++++++++++- data/quiz-bank.json | 45 +++++++++++++++++++++++++++++++++++ scripts/track-command.sh | 51 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 125 insertions(+), 1 deletion(-) diff --git a/data/concept-tree.json b/data/concept-tree.json index eabc9eb..e4a312a 100644 --- a/data/concept-tree.json +++ b/data/concept-tree.json @@ -337,8 +337,36 @@ "triggers": ["cache", "redis", "CDN", "load balancer"] } } + }, + "debugging": { + "name": "Debugging", + "icon": "🐛", + "belt_requirement": "white", + "concepts": { + "error-reading": { + "name": "Error Reading", + "xp_to_master": 15, + "prerequisites": [], + "description": "Understanding error messages and stack traces", + "triggers": ["error", "Error:", "Traceback", "ENOENT", "stack trace"] + }, + "debugging-mindset": { + "name": "Debugging Mindset", + "xp_to_master": 20, + "prerequisites": ["error-reading"], + "description": "Systematic approach to finding and fixing bugs", + "triggers": ["debug", "breakpoint", "console.log", "print("] + }, + "common-errors": { + "name": "Common Errors", + "xp_to_master": 25, + "prerequisites": ["error-reading", "debugging-mindset"], + "description": "Recognizing and fixing common programming errors", + "triggers": ["TypeError", "ReferenceError", "SyntaxError", "undefined", "null"] + } + } } }, - "total_concepts": 42, + "total_concepts": 45, "mastery_quiz_threshold": 3 } diff --git a/data/quiz-bank.json b/data/quiz-bank.json index b149e6d..3e90271 100644 --- a/data/quiz-bank.json +++ b/data/quiz-bank.json @@ -217,6 +217,51 @@ "expected_understanding": "Docker packages an app with all its dependencies so it runs the same everywhere. Solves 'works on my machine' problems by creating consistent environments.", "hint": "Think about shipping a complete kitchen vs. just a recipe." } + ], + "error-reading": [ + { + "belt": "white", + "question": "What does a stack trace show you?", + "options": ["The list of files in your project", "The sequence of function calls that led to the error", "All the variables currently in memory"], + "correct": 1, + "explanation": "A stack trace is like a breadcrumb trail — it shows every function that was called, in order, up until the crash. Reading it from bottom to top tells you where the error originated and how the code got there.", + "hint": "Think of it as a history of every step the program took before it fell over." + }, + { + "belt": "white", + "question": "What is the first thing you should read in an error message?", + "options": ["The line number at the very bottom", "The error type and the first line of the message", "The full stack trace from top to bottom"], + "correct": 1, + "explanation": "The error type (like `TypeError` or `SyntaxError`) and the first descriptive line tell you WHAT went wrong. Once you know what, you can use the line number and stack trace to figure out WHERE. Start at the top, not the bottom.", + "hint": "Skimming a news article — you read the headline first, then the details." + } + ], + "debugging-mindset": [ + { + "belt": "yellow", + "question": "What is the most effective first step when debugging?", + "options": ["Delete the code and rewrite it from scratch", "Reproduce the error reliably, then read the error message carefully", "Ask someone else to fix it"], + "correct": 1, + "explanation": "You can't fix what you can't consistently reproduce. Once you can make the bug happen on demand, read the error message carefully — it usually tells you exactly what went wrong and where. Only after understanding the error should you start changing code.", + "hint": "A doctor diagnoses before prescribing. What's the equivalent first step for a bug?" + } + ], + "common-errors": [ + { + "belt": "orange", + "question": "What typically causes a ReferenceError in JavaScript?", + "options": ["Using the wrong data type in a calculation", "Trying to use a variable that hasn't been declared or is out of scope", "A typo in an HTML tag"], + "correct": 1, + "explanation": "A `ReferenceError` means JavaScript looked for a variable name and couldn't find it anywhere in scope. Common causes: misspelling the variable name, using it before declaring it, or accessing it outside the block where it was defined.", + "hint": "The error name says 'reference' — what does it mean when a reference points to nothing?" + }, + { + "belt": "orange", + "format": "free_response", + "question": "What's the difference between a syntax error and a runtime error? Give an example of each.", + "expected_understanding": "A syntax error is caught before the code runs — it's a grammar mistake the interpreter can't parse (e.g., missing closing bracket). A runtime error happens while the code is running, when an operation fails on valid-looking code (e.g., calling a method on null).", + "hint": "Think about the difference between a grammatically incorrect sentence and a sentence that makes sense but describes something impossible." + } ] } } diff --git a/scripts/track-command.sh b/scripts/track-command.sh index 57134a8..0949053 100755 --- a/scripts/track-command.sh +++ b/scripts/track-command.sh @@ -69,9 +69,60 @@ if command -v jq &> /dev/null; then # Sanitize command for JSON (remove quotes and special chars) SAFE_CMD=$(echo "$COMMAND" | head -c 80 | tr '"' "'" | tr '\\' '/') + # Check if this is a test runner command — skip error detection for those + IS_TEST_RUNNER="false" + case "$COMMAND" in + jest\ *|"jest"|\ + pytest\ *|"pytest"|\ + vitest\ *|"vitest"|\ + bats\ *|"bats"|\ + mocha\ *|"mocha"|\ + "npm test"*|"npm run test"*|\ + "yarn test"*|"yarn run test"*|\ + "npx jest"*|"npx vitest"*) + IS_TEST_RUNNER="true" + ;; + esac + + # Detect error patterns in command output (only for non-test-runner commands) + ERROR_CONCEPT="" + if [ "$IS_TEST_RUNNER" = "false" ]; then + TOOL_RESPONSE=$(echo "$INPUT" | jq -r '.tool_response // ""' 2>/dev/null || echo "") + STDOUT=$(echo "$INPUT" | jq -r '.tool_response.stdout // ""' 2>/dev/null || echo "") + STDERR=$(echo "$INPUT" | jq -r '.tool_response.stderr // ""' 2>/dev/null || echo "") + OUTPUT="${STDOUT}${STDERR}${TOOL_RESPONSE}" + + case "$OUTPUT" in + *"Traceback (most recent call last):"*) + ERROR_CONCEPT="error-reading" + ;; + *"TypeError"*|*"ReferenceError"*|*"SyntaxError"*) + ERROR_CONCEPT="common-errors" + ;; + *"Error:"*|*"ERROR:"*|*"error:"*) + ERROR_CONCEPT="error-reading" + ;; + *"ENOENT"*|*"EACCES"*|*"EPERM"*) + ERROR_CONCEPT="error-reading" + ;; + *"command not found"*) + ERROR_CONCEPT="error-reading" + ;; + *"ModuleNotFoundError"*|*"ImportError"*) + ERROR_CONCEPT="error-reading" + ;; + *"fatal:"*) + ERROR_CONCEPT="error-reading" + ;; + esac + fi + if [ "$IS_FIRST_EVER" = "true" ] && [ -n "$CONCEPT" ]; then # First-time encounter: micro-lesson about the concept CONTEXT="🥋 CodeSensei micro-lesson trigger: The user just encountered '$CONCEPT' for the FIRST TIME (command: $SAFE_CMD). Their belt level is '$BELT'. Provide a brief 2-sentence explanation of what $CONCEPT means and why it matters. Adapt language to their belt level. Keep it concise and non-intrusive." + elif [ -n "$ERROR_CONCEPT" ]; then + # Error detected in command output: teach debugging + CONTEXT="🥋 CodeSensei inline insight: An error appeared in the command output ($SAFE_CMD). The user's belt level is '$BELT'. This is a great moment to teach '$ERROR_CONCEPT' — briefly explain how to read and interpret this type of error in 1-2 sentences, adapted to their belt level. Keep it supportive and practical." elif [ -n "$CONCEPT" ]; then # Already-seen concept: brief inline insight about this specific command CONTEXT="🥋 CodeSensei inline insight: Claude just ran a '$CONCEPT' command ($SAFE_CMD). The user's belt level is '$BELT'. Provide a brief 1-sentence explanation of what this command does, adapted to their belt level. Keep it natural and non-intrusive." From 82c29bdbbd9c295c7aece4dce81a1df93afd5197 Mon Sep 17 00:00:00 2001 From: bitfalt <75431447+bitfalt@users.noreply.github.com> Date: Mon, 9 Mar 2026 14:00:08 -0600 Subject: [PATCH 2/2] fix: persist debugging concepts from hook errors --- scripts/track-command.sh | 55 +++++++++++++++++++++++++++------------- 1 file changed, 37 insertions(+), 18 deletions(-) diff --git a/scripts/track-command.sh b/scripts/track-command.sh index 0949053..062e01f 100755 --- a/scripts/track-command.sh +++ b/scripts/track-command.sh @@ -47,22 +47,35 @@ if command -v jq &> /dev/null; then # Log the command echo "{\"timestamp\":\"$TIMESTAMP\",\"command\":\"$(echo "$COMMAND" | head -c 200)\",\"concept\":\"$CONCEPT\"}" >> "$COMMANDS_LOG" - # Track concept in session and lifetime if new and meaningful - IS_FIRST_EVER="false" - if [ -n "$CONCEPT" ] && [ -f "$PROFILE_FILE" ]; then - ALREADY_SEEN=$(jq --arg c "$CONCEPT" '.session_concepts | index($c)' "$PROFILE_FILE") - if [ "$ALREADY_SEEN" = "null" ]; then - UPDATED=$(jq --arg c "$CONCEPT" '.session_concepts += [$c]' "$PROFILE_FILE") - echo "$UPDATED" > "$PROFILE_FILE" + record_profile_concept() { + local concept="$1" + local first_flag_var="$2" + + if [ -z "$concept" ] || [ ! -f "$PROFILE_FILE" ]; then + return + fi + + local already_in_session + local already_in_lifetime + local updated + + already_in_session=$(jq --arg c "$concept" '.session_concepts | index($c)' "$PROFILE_FILE") + if [ "$already_in_session" = "null" ]; then + updated=$(jq --arg c "$concept" '.session_concepts += [$c]' "$PROFILE_FILE") + echo "$updated" > "$PROFILE_FILE" fi - LIFETIME_SEEN=$(jq --arg c "$CONCEPT" '.concepts_seen | index($c)' "$PROFILE_FILE") - if [ "$LIFETIME_SEEN" = "null" ]; then - UPDATED=$(jq --arg c "$CONCEPT" '.concepts_seen += [$c]' "$PROFILE_FILE") - echo "$UPDATED" > "$PROFILE_FILE" - IS_FIRST_EVER="true" + already_in_lifetime=$(jq --arg c "$concept" '.concepts_seen | index($c)' "$PROFILE_FILE") + if [ "$already_in_lifetime" = "null" ]; then + updated=$(jq --arg c "$concept" '.concepts_seen += [$c]' "$PROFILE_FILE") + echo "$updated" > "$PROFILE_FILE" + printf -v "$first_flag_var" '%s' "true" fi - fi + } + + # Track concept in session and lifetime if new and meaningful + IS_FIRST_EVER="false" + record_profile_concept "$CONCEPT" IS_FIRST_EVER # Always inject teaching context after commands BELT=$(jq -r '.belt // "white"' "$PROFILE_FILE" 2>/dev/null || echo "white") @@ -79,7 +92,8 @@ if command -v jq &> /dev/null; then mocha\ *|"mocha"|\ "npm test"*|"npm run test"*|\ "yarn test"*|"yarn run test"*|\ - "npx jest"*|"npx vitest"*) + "pnpm test"*|"pnpm run test"*|"pnpm vitest"*|\ + "npx jest"*|"npx vitest"*|"pnpm exec jest"*|"pnpm exec vitest"*) IS_TEST_RUNNER="true" ;; esac @@ -117,12 +131,17 @@ if command -v jq &> /dev/null; then esac fi - if [ "$IS_FIRST_EVER" = "true" ] && [ -n "$CONCEPT" ]; then - # First-time encounter: micro-lesson about the concept - CONTEXT="🥋 CodeSensei micro-lesson trigger: The user just encountered '$CONCEPT' for the FIRST TIME (command: $SAFE_CMD). Their belt level is '$BELT'. Provide a brief 2-sentence explanation of what $CONCEPT means and why it matters. Adapt language to their belt level. Keep it concise and non-intrusive." + ERROR_IS_FIRST_EVER="false" + record_profile_concept "$ERROR_CONCEPT" ERROR_IS_FIRST_EVER + + if [ "$ERROR_IS_FIRST_EVER" = "true" ] && [ -n "$ERROR_CONCEPT" ]; then + CONTEXT="🥋 CodeSensei micro-lesson trigger: The user just encountered '$ERROR_CONCEPT' for the FIRST TIME while reading command output ($SAFE_CMD). Their belt level is '$BELT'. Provide a brief 2-sentence explanation of how to read this kind of error and why it matters. Adapt language to their belt level. Keep it supportive and practical." elif [ -n "$ERROR_CONCEPT" ]; then - # Error detected in command output: teach debugging + # Error detected in command output: teach debugging first CONTEXT="🥋 CodeSensei inline insight: An error appeared in the command output ($SAFE_CMD). The user's belt level is '$BELT'. This is a great moment to teach '$ERROR_CONCEPT' — briefly explain how to read and interpret this type of error in 1-2 sentences, adapted to their belt level. Keep it supportive and practical." + elif [ "$IS_FIRST_EVER" = "true" ] && [ -n "$CONCEPT" ]; then + # First-time encounter: micro-lesson about the concept + CONTEXT="🥋 CodeSensei micro-lesson trigger: The user just encountered '$CONCEPT' for the FIRST TIME (command: $SAFE_CMD). Their belt level is '$BELT'. Provide a brief 2-sentence explanation of what $CONCEPT means and why it matters. Adapt language to their belt level. Keep it concise and non-intrusive." elif [ -n "$CONCEPT" ]; then # Already-seen concept: brief inline insight about this specific command CONTEXT="🥋 CodeSensei inline insight: Claude just ran a '$CONCEPT' command ($SAFE_CMD). The user's belt level is '$BELT'. Provide a brief 1-sentence explanation of what this command does, adapted to their belt level. Keep it natural and non-intrusive."