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..062e01f 100755 --- a/scripts/track-command.sh +++ b/scripts/track-command.sh @@ -47,29 +47,99 @@ 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 - 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" + 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 - fi + + 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 + } + + # 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") # Sanitize command for JSON (remove quotes and special chars) SAFE_CMD=$(echo "$COMMAND" | head -c 80 | tr '"' "'" | tr '\\' '/') - if [ "$IS_FIRST_EVER" = "true" ] && [ -n "$CONCEPT" ]; then + # 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"*|\ + "pnpm test"*|"pnpm run test"*|"pnpm vitest"*|\ + "npx jest"*|"npx vitest"*|"pnpm exec jest"*|"pnpm exec 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 + + 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 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