===== .devcontainer/devcontainer.json ===== { "name": "Dojo Ascension", "image": "mcr.microsoft.com/devcontainers/python:3.11", "features": { "ghcr.io/devcontainers/features/git:1": {} }, "postCreateCommand": "pip install --user -r requirements.txt", "customizations": { "vscode": { "extensions": [ "ms-python.python", "eamodio.gitlens", "esbenp.prettier-vscode" ], "settings": { "python.defaultInterpreterPath": "/usr/local/bin/python" } } }, "postAttachCommand": "python newcomer.py", "remoteUser": "vscode" } ===== .github/ISSUE_TEMPLATE/bug_report.md ===== --- name: Bug report about: Create a report to help us improve title: '' labels: '' assignees: '' --- **Describe the bug** A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the behavior: 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' 4. See error **Expected behavior** A clear and concise description of what you expected to happen. **Screenshots** If applicable, add screenshots to help explain your problem. **Desktop (please complete the following information):** - OS: [e.g. iOS] - Browser [e.g. chrome, safari] - Version [e.g. 22] **Smartphone (please complete the following information):** - Device: [e.g. iPhone6] - OS: [e.g. iOS8.1] - Browser [e.g. stock browser, safari] - Version [e.g. 22] **Additional context** Add any other context about the problem here. ===== .github/ISSUE_TEMPLATE/feature_request.md ===== --- name: Feature request about: Suggest an idea for this project title: '' labels: '' assignees: '' --- **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] **Describe the solution you'd like** A clear and concise description of what you want to happen. **Describe alternatives you've considered** A clear and concise description of any alternative solutions or features you've considered. **Additional context** Add any other context or screenshots about the feature request here. ===== .github/ISSUE_TEMPLATE/pilot_feedback.md ===== --- name: Pilot feedback about: Capture cohort outcomes, friction points, and inclusion signals title: 'pilot: ' labels: '' assignees: '' --- **Cohort / organization** **Session date(s)** **How many learners participated?** **How many learners completed at least one mission?** **What was clear?** **What was confusing?** **What made learners feel included or excluded?** **Did anyone return for another session within 14 days?** **Did any participants open issues, PRs, or draft missions?** **What should change before the next cohort?** ===== .github/pull_request_template.md ===== ## Summary Describe what this PR changes and why. ## Type of Change - [ ] Mission/content only - [ ] Code change - [ ] Documentation only ## Quality Baseline (required) - [ ] `python -m unittest discover -s tests` passes - [ ] `python validate_missions.py` passes - [ ] I completed the relevant checklist(s) below ## Definition of Done โ€” Mission/Content PRs - [ ] Mission content is clear and learner-friendly - [ ] Mission follows `missions/mission_template.json` - [ ] Reviewed against `missions/MISSION_REVIEW_RUBRIC.md` - [ ] Accessibility baseline considered (`docs/ACCESSIBILITY_BASELINE.md`) ## Definition of Done โ€” Web Frontend Changes - [ ] Keyboard flow verified (Tab/Shift+Tab/Enter/Space) - [ ] Contrast/readability checked - [ ] Small-screen readability checked ## Links - Related issue(s): - Related docs/roadmap updates: ===== .github/workflows/refresh-repo-bundle.yml ===== name: Refresh Repo Bundle on: push: branches: [main] permissions: contents: write jobs: refresh-bundle: # Future opportunity: fold this into validate.yml if we want one combined CI workflow. if: github.actor != 'github-actions[bot]' runs-on: ubuntu-latest steps: - name: Check out repository uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: '3.11' - name: Refresh repo bundle run: python refresh_repo_bundle.py - name: Detect bundle changes id: changes run: | if git diff --quiet -- repo_bundle.txt; then echo "changed=false" >> "$GITHUB_OUTPUT" else echo "changed=true" >> "$GITHUB_OUTPUT" fi - name: Commit and push updated bundle if: steps.changes.outputs.changed == 'true' run: | git config user.name "github-actions[bot]" git config user.email "41898282+github-actions[bot]@users.noreply.github.com" git add repo_bundle.txt git commit -m "chore: refresh repo bundle [skip ci]" git push ===== .github/workflows/validate.yml ===== name: Validate Dojo Ascension on: push: branches: [main, master] pull_request: jobs: validate: runs-on: ubuntu-latest steps: - name: Check out repository uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: '3.11' - name: Install dependencies run: | python -m pip install --upgrade pip pip install -r requirements.txt - name: Run unit tests run: python -m unittest discover -s tests - name: Validate mission files run: python validate_missions.py ===== .gitignore ===== # ========================================== # Dojo Ascension v4.0 - Git Ignore Rules # ========================================== # Virtual Environments venv/ env/ .env .venv/ # Python Artifacts __pycache__/ *.py[cod] *$py.class *.so # Dojo Ascension Specifics # Prevents learners from committing their local progress files .dojo_save.json *.dojo_save.json # OS Generated Files .DS_Store .DS_Store? ._* .Spotlight-V100 .Trashes ehthumbs.db Thumbs.db # IDEs and Editors .vscode/ .idea/ *.swp *.swo ===== 52_card_deck_minigame.md ===== # ๐Ÿƒ Dojo Ascension โ€” 52-Card Memory Deck (v2) > **Covers:** Python ยท JSON ยท JavaScript ยท HTML ยท GitHub Workflows > **For:** Young learners, Dojo Ascension curriculum, physical or digital flashcard play *** ## How to Play **Memory Match** โ€” Lay all cards face-down. Flip two at a time. Match concept card to its code/answer card. **Solo Drill** โ€” Read the **Front** (concept + visual + snippet). Flip for the **Back** (metaphor + exercise). **Dojo Mission Unlock** โ€” Complete a dojo mission โ†’ earn the matching card. Track mastered cards in your save file using stable IDs. **Practice Chain** โ€” How many days in a row can you answer every card in a suit? Track your continuity, not your streak. **Tier Progression** โ€” Play only ๐ŸŸข Beginner cards first. Unlock ๐ŸŸก Intermediate after completing a Beginner run. Unlock ๐Ÿ”ด Advanced last. *** ## PYTHON ๐Ÿ **Suit of Roots.** Python is the soil โ€” every other technology grows from here. | Tier | Cards | Learning Outcome | |------|-------|------------------| | ๐ŸŸข Beginner | Aโ€“6 | Variables, print, strings, numbers, booleans, if/else | | ๐ŸŸก Intermediate | 7โ€“J | Loops, functions, lists, dictionaries | | ๐Ÿ”ด Advanced | Qโ€“K | Imports, OOP / Class | *** ### ๐ŸŸข A โ€” Variable `[Beginner]` **๐ŸŽด FRONT** ``` ๐Ÿ“ฆ [ seed = 42 ] ``` ```python seed = 42 name = 'David' ``` **๐Ÿ”„ BACK** *A named vessel. Like a seed โ€” holds potential until called.* ๐ŸŽฏ Try it: Open your terminal. Type `seed = 42` then `print(seed)`. What appears? *** ### ๐ŸŸข 2 โ€” Print `[Beginner]` **๐ŸŽด FRONT** ``` ๐Ÿ–ฅ๏ธ โ†’ 'Hello, Dojo!' ``` ```python print("Hello, Dojo!") print("Honor:", 42) ``` **๐Ÿ”„ BACK** *Your first voice in code. Terminal listens when you speak.* ๐ŸŽฏ Try it: Print your own name and your city in two separate print() calls. *** ### ๐ŸŸข 3 โ€” String `[Beginner]` **๐ŸŽด FRONT** ``` "hello" โ† quotes = text ``` ```python greeting = "Dojo" full = "Hello " + greeting ``` **๐Ÿ”„ BACK** *Text wrapped in quotes. Words are data too.* ๐ŸŽฏ Try it: Create a variable `dojo_name` with your dojo's name. Concatenate it with 'Welcome to '. *** ### ๐ŸŸข 4 โ€” Integer & Float `[Beginner]` **๐ŸŽด FRONT** ``` 42 = int ยท 3.14 = float ``` ```python honor = 240 confidence = 0.75 ``` **๐Ÿ”„ BACK** *Whole vs. fractional. Honor points are integers; mastery is a float.* ๐ŸŽฏ Try it: Create `honor = 50`. Multiply it by 1.5. Print the result. What type is it now? *** ### ๐ŸŸข 5 โ€” Boolean `[Beginner]` **๐ŸŽด FRONT** ``` True โ— โ—‹ False ``` ```python is_ready = True is_done = False print(type(is_ready)) ``` **๐Ÿ”„ BACK** *Binary heartbeat. Every decision reduces to True or False.* ๐ŸŽฏ Try it: Type `print(5 > 3)` and `print(5 < 3)`. What do you get? Why? *** ### ๐ŸŸข 6 โ€” If / Else `[Beginner]` **๐ŸŽด FRONT** ``` honor > 100? โœ… yes โ†’ level_up() โŒ no โ†’ keep_going() ``` ```python if honor > 100: level_up() else: keep_going() ``` **๐Ÿ”„ BACK** *The dojo chooses its own path. Decision branches are forks in the road.* ๐ŸŽฏ Try it: Write an if/else that prints 'Apprentice' if honor >= 50, else prints 'Initiate'. *** ### ๐ŸŸก 7 โ€” For Loop `[Intermediate]` **๐ŸŽด FRONT** ``` skills = ['git','py','json'] โ†’ git โ†’ py โ†’ json ``` ```python skills = ['git', 'py', 'json'] for s in skills: print('Practicing:', s) ``` **๐Ÿ”„ BACK** *The dojo drill. Repetition with purpose โ€” each rep builds a layer.* ๐ŸŽฏ Try it: Create a list of 3 things you want to learn. Loop through and print each one. *** ### ๐ŸŸก 8 โ€” While Loop `[Intermediate]` **๐ŸŽด FRONT** ``` while honor < 100: โ†ป train() ``` ```python honor = 0 while honor < 50: honor += 10 print('Honor:', honor) ``` **๐Ÿ”„ BACK** *Loops until the condition breaks. Persistence in code form.* ๐ŸŽฏ Try it: Write a while loop that doubles a number starting at 1 until it exceeds 100. Count the steps. *** ### ๐ŸŸก 9 โ€” Function (def) `[Intermediate]` **๐ŸŽด FRONT** ``` def greet(name) input โ†’ logic โ†’ output ``` ```python def greet(name): return 'Hello, ' + name print(greet('David')) ``` **๐Ÿ”„ BACK** *Jeet Kune Do: write once, strike infinitely. Maximum efficiency.* ๐ŸŽฏ Try it: Write a function `add_honor(current, points)` that returns the new total. Call it 3 times. *** ### ๐ŸŸก 10 โ€” List `[Intermediate]` **๐ŸŽด FRONT** ``` [ 'git', 'py', 'json' ] 0 1 2 ``` ```python tools = ['git', 'python', 'json'] tools.append('html') print(tools[0]) ``` **๐Ÿ”„ BACK** *A dojo roster. Ordered, indexable, growable.* ๐ŸŽฏ Try it: Make a list of your top 3 skills. Append a 4th. Print the length with len(). *** ### ๐ŸŸก J โ€” Dictionary `[Intermediate]` **๐ŸŽด FRONT** ``` { 'skill': 'git' 'level': 3 } ``` ```python player = {'name': 'David', 'rank': 'Adept'} print(player['rank']) player['honor'] = 90 ``` **๐Ÿ”„ BACK** *The character sheet. Keys unlock values โ€” like a map of your skills.* ๐ŸŽฏ Try it: Build a dict with your name, city, and one skill. Add a 'level' key set to 1. *** ### ๐Ÿ”ด Q โ€” Import `[Advanced]` **๐ŸŽด FRONT** ``` import json โ””โ”€ borrowed mastery ``` ```python import json import os from pathlib import Path ``` **๐Ÿ”„ BACK** *Calling in a specialist. The open-source community is your extended dojo.* ๐ŸŽฏ Try it: `import math` then print `math.pi` and `math.sqrt(144)`. What are the results? *** ### ๐Ÿ”ด K โ€” Class `[Advanced]` **๐ŸŽด FRONT** ``` class Player: blueprint โ†“ p = Player() instance ``` ```python class Player: def __init__(self, name): self.name = name self.honor = 0 p = Player('David') print(p.name) ``` **๐Ÿ”„ BACK** *The franchise model. Class = blueprint. Object = your neighborhood bakery.* ๐ŸŽฏ Try it: Add an `add_honor(pts)` method to the Player class. Create a player and call it twice. *** ## JSON ๐Ÿ“ฆ **Suit of Memory.** JSON is how programs remember, communicate, and persist. | Tier | Cards | Learning Outcome | |------|-------|------------------| | ๐ŸŸข Beginner | Aโ€“6 | JSON syntax rules, objects, arrays, types | | ๐ŸŸก Intermediate | 7โ€“J | Nested data, loads/dumps, file read/write | | ๐Ÿ”ด Advanced | Qโ€“K | Stable IDs, data-driven mission architecture | *** ### ๐ŸŸข A โ€” What is JSON? `[Beginner]` **๐ŸŽด FRONT** ``` { "key": "value" } Universal trade language ``` ```python { "name": "David", "rank": "Apprentice" } ``` **๐Ÿ”„ BACK** *The fiat currency of data exchange. Every system speaks JSON.* ๐ŸŽฏ Try it: Spot the error: `{'name': 'David'}` โ€” why won't this work as JSON? (hint: quote style) *** ### ๐ŸŸข 2 โ€” Object (curly braces) `[Beginner]` **๐ŸŽด FRONT** ``` { } = object "key": value inside ``` ```python { "player": "David", "honor": 240 } ``` **๐Ÿ”„ BACK** *Your save file. Curly braces hold your character's entire state.* ๐ŸŽฏ Try it: Write a JSON object for yourself: name, city, one skill, and honor points set to 0. *** ### ๐ŸŸข 3 โ€” Array (square brackets) `[Beginner]` **๐ŸŽด FRONT** ``` [ "git", "python", "html" ] 0 1 2 ``` ```python { "skills": ["git", "python", "json"] } ``` **๐Ÿ”„ BACK** *A skill inventory. Ordered, indexed, ready to iterate.* ๐ŸŽฏ Try it: Add a JSON array called 'completed_missions' with 3 mission IDs (use stable string IDs, not numbers). *** ### ๐ŸŸข 4 โ€” String Values `[Beginner]` **๐ŸŽด FRONT** ``` "always" double quotes not 'single' ``` ```python { "name": "David", "city": "SLC" } ``` **๐Ÿ”„ BACK** *JSON is strict. Double quotes only โ€” no exceptions. Discipline is a feature.* ๐ŸŽฏ Try it: Find 2 errors: `{name: 'David', 'city': "SLC"}` and fix them. *** ### ๐ŸŸข 5 โ€” Number Values `[Beginner]` **๐ŸŽด FRONT** ``` { "honor": 240 } no quotes on numbers ``` ```python { "honor": 240, "confidence": 0.75 } ``` **๐Ÿ”„ BACK** *Raw numbers speak plainly โ€” no quotation needed. Truth doesn't need decoration.* ๐ŸŽฏ Try it: Which is wrong? `{"score": 100}` or `{"score": "100"}` โ€” when does it matter? *** ### ๐ŸŸข 6 โ€” Boolean & Null `[Beginner]` **๐ŸŽด FRONT** ``` true / false (lowercase!) null = empty vessel ``` ```python { "active": true, "retired": false, "mentor": null } ``` **๐Ÿ”„ BACK** *Lowercase in JSON โ€” not Python's True/False. A small difference that breaks everything.* ๐ŸŽฏ Try it: Convert this Python dict to valid JSON by hand: `{'active': True, 'data': None}` *** ### ๐ŸŸก 7 โ€” Nested Object `[Intermediate]` **๐ŸŽด FRONT** ``` { "player": { "name": "David" "rank": "Adept" } } ``` ```python { "player": { "name": "David", "skills": {"git": 3, "python": 4} } } ``` **๐Ÿ”„ BACK** *Objects inside objects. Genealogy of data โ€” tracing lineage through nested layers.* ๐ŸŽฏ Try it: Access the git skill level from the snippet above using Python. Write the full path: `data['player']['skills']['git']` *** ### ๐ŸŸก 8 โ€” json.loads() `[Intermediate]` **๐ŸŽด FRONT** ``` string โ†’ ๐Ÿ”“ โ†’ dict json.loads(text) ``` ```python import json text = '{"honor": 90}' data = json.loads(text) print(data["honor"]) ``` **๐Ÿ”„ BACK** *Decoding a message. Raw text becomes a living Python structure.* ๐ŸŽฏ Try it: Take a JSON string `'{"level": 3}'`, parse it, and print the value plus 1. *** ### ๐ŸŸก 9 โ€” json.dumps() `[Intermediate]` **๐ŸŽด FRONT** ``` dict โ†’ ๐Ÿ”’ โ†’ string json.dumps(data) ``` ```python import json data = {'rank': 'Adept'} text = json.dumps(data, indent=2) print(text) ``` **๐Ÿ”„ BACK** *Encoding for travel. Python dict becomes portable JSON text.* ๐ŸŽฏ Try it: Create a Python dict of your skills, convert to JSON string with indent=2, and print it. *** ### ๐ŸŸก 10 โ€” Read JSON File `[Intermediate]` **๐ŸŽด FRONT** ``` ๐Ÿ“‚ save.json โ†’ json.load(f) โ†’ dict in memory ``` ```python import json with open("save.json", "r") as f: data = json.load(f) print(data) ``` **๐Ÿ”„ BACK** *Reading institutional memory. The dojo remembers who trained and when.* ๐ŸŽฏ Try it: Create a file `test.json` with `{"name": "you"}`. Write Python to read and print the name. *** ### ๐ŸŸก J โ€” Write JSON File `[Intermediate]` **๐ŸŽด FRONT** ``` dict in memory โ†’ json.dump(f) โ†’ ๐Ÿ’พ save.json ``` ```python import json data = {"honor": 50, "rank": "Initiate"} with open("save.json", "w") as f: json.dump(data, f, indent=2) ``` **๐Ÿ”„ BACK** *Writing your legacy to disk. Progress that survives closing the terminal.* ๐ŸŽฏ Try it: Save your own player dict to a file. Close Python. Reopen and read it back. Did it persist? *** ### ๐Ÿ”ด Q โ€” Stable ID Pattern `[Advanced]` **๐ŸŽด FRONT** ``` "id": "git-branching" not "id": 3 IDs never change. Numbers do. ``` ```python { "id": "git-branching", "number": 6, "title": "Git Branching" } ``` **๐Ÿ”„ BACK** *Names outlive positions. A dojo belt has a name, not just a number in line.* ๐ŸŽฏ Try it: Refactor a save file that uses `'completed': [1,3,5]` to use stable string IDs. Why is this safer? *** ### ๐Ÿ”ด K โ€” JSON as Mission Pack `[Advanced]` **๐ŸŽด FRONT** ``` missions.json engine.py loads โ†— educators edit โ†— no Python needed ``` ```python [ { "id": "py-variables", "skill": "python", "challenge": "Assign 42 to seed", "answer": "seed = 42" } ] ``` **๐Ÿ”„ BACK** *Content separated from engine. Anyone can add missions โ€” coders, teachers, community members.* ๐ŸŽฏ Try it: Write a new mission card in JSON for a concept YOU learned this week. Share it as a PR. *** ## JAVASCRIPT & HTML ๐ŸŒ **Suit of Expression.** HTML gives structure; JavaScript gives behavior. | Tier | Cards | Learning Outcome | |------|-------|------------------| | ๐ŸŸข Beginner | Aโ€“6 | HTML boilerplate, headings, text, links, images, divs | | ๐ŸŸก Intermediate | 7โ€“J | JS variables, functions, if/else, DOM, events | | ๐Ÿ”ด Advanced | Qโ€“K | Fetch API, debugging with console | *** ### ๐ŸŸข A โ€” HTML Boilerplate `[Beginner]` **๐ŸŽด FRONT** ``` ๐Ÿง  ๐Ÿ‘๏ธ ``` ```js Dojo

Hello, Dojo!

``` **๐Ÿ”„ BACK** *The skeleton every page is built on. Structure before style.* ๐ŸŽฏ Try it: Create `index.html` with this boilerplate. Add your name in an

. Open it in a browser. *** ### ๐ŸŸข 2 โ€” Heading Tags `[Beginner]` **๐ŸŽด FRONT** ```

BIG

Medium

smaller

tiny ``` ```js

The Dojo

Python Suit

Variables

``` **๐Ÿ”„ BACK** *Six levels of hierarchy. Structure is meaning โ€” headings tell the browser what matters most.* ๐ŸŽฏ Try it: Build an outline of this card deck using h1 for the deck name and h2 for each suit. *** ### ๐ŸŸข 3 โ€” Paragraph & Text Tags `[Beginner]` **๐ŸŽด FRONT** ```

block of text

bold italic ``` ```js

Welcome to the Dojo.

Practice daily.

``` **๐Ÿ”„ BACK** *Words need homes. Paragraphs give text breathing room and meaning.* ๐ŸŽฏ Try it: Write a 2-sentence paragraph about why you're learning to code. Bold one key word. *** ### ๐ŸŸข 4 โ€” Link Tag `[Beginner]` **๐ŸŽด FRONT** ``` Click โ† anchor text ``` ```js GitHub Home ``` **๐Ÿ”„ BACK** *The hyperlink โ€” the web's most powerful primitive. One tag connects the whole internet.* ๐ŸŽฏ Try it: Add a link in your HTML to the Dojo Ascension GitHub repo. Open it. Does it work? *** ### ๐ŸŸข 5 โ€” Image Tag `[Beginner]` **๐ŸŽด FRONT** ``` desc self-closing ยท alt = required ``` ```js Dojo crest Logo ``` **๐Ÿ”„ BACK** *Alt text is respect for all learners โ€” screen readers depend on it.* ๐ŸŽฏ Try it: Add an image to your page. What happens if you misspell the src? What does the alt text show? *** ### ๐ŸŸข 6 โ€” div & span `[Beginner]` **๐ŸŽด FRONT** ```
= ๐Ÿ“ฆ block (full row) = ๐Ÿ”ค inline (within text) ``` ```js
A ๐Ÿ
``` **๐Ÿ”„ BACK** *Rooms and words inside rooms. divs structure layout; spans style specific words.* ๐ŸŽฏ Try it: Wrap three card titles in divs with class='card'. Style them with a border using inline CSS. *** ### ๐ŸŸก 7 โ€” JS Variables `[Intermediate]` **๐ŸŽด FRONT** ``` let = changeable ๐Ÿ”„ const = fixed ๐Ÿ”’ var = avoid (old) ``` ```js let rank = 'Apprentice'; const MAX_HONOR = 500; rank = 'Practitioner'; // โœ… MAX_HONOR = 600; // โŒ Error ``` **๐Ÿ”„ BACK** *let changes, const never does. Name your constraints โ€” it prevents bugs.* ๐ŸŽฏ Try it: Open browser console (F12). Declare a `let` and change it. Try changing a `const`. What error appears? *** ### ๐ŸŸก 8 โ€” JS Function `[Intermediate]` **๐ŸŽด FRONT** ``` function name(input) { // logic return output; } ``` ```js function addHonor(current, pts) { return current + pts; } console.log(addHonor(50, 10)); ``` **๐Ÿ”„ BACK** *Same Jeet Kune Do principle โ€” write once, use anywhere. JS uses {} instead of indentation.* ๐ŸŽฏ Try it: Write a JS function `greetPlayer(name, rank)` that returns 'Hello [name], rank [rank]'. *** ### ๐ŸŸก 9 โ€” JS If/Else `[Intermediate]` **๐ŸŽด FRONT** ``` if (honor > 100) { โœ… unlock() } else { โŒ train() } ``` ```js let honor = 120; if (honor > 100) { console.log('Rank up!'); } else { console.log('Keep training.'); } ``` **๐Ÿ”„ BACK** *Same decision logic as Python โ€” curly braces replace indentation. The thought is identical.* ๐ŸŽฏ Try it: Write an if/else/else-if in JS that prints a rank name based on honor: <50 Initiate, <100 Apprentice, else Practitioner. *** ### ๐ŸŸก 10 โ€” DOM Select `[Intermediate]` **๐ŸŽด FRONT** ``` HTML element โ†‘ document.querySelector('#id') โ†‘ JS control ``` ```js const card = document.querySelector('#card-1'); const allCards = document.querySelectorAll('.card'); console.log(card.textContent); ``` **๐Ÿ”„ BACK** *The bridge between HTML and JS. Select an element, then control it.* ๐ŸŽฏ Try it: In your browser console, run `document.querySelector('h1').textContent`. What do you see? Change it. *** ### ๐ŸŸก J โ€” Event Listener `[Intermediate]` **๐ŸŽด FRONT** ``` btn ๐Ÿ–ฑ๏ธ click โ†’ function fires โ†’ page responds ``` ```js const btn = document.querySelector('#flip-btn'); btn.addEventListener('click', () => { card.classList.toggle('flipped'); }); ``` **๐Ÿ”„ BACK** *Code that waits, then responds. Reactive thinking โ€” don't act until the moment calls.* ๐ŸŽฏ Try it: Add a button to your HTML. Write JS so clicking it toggles 'visible' class on a hidden div. *** ### ๐Ÿ”ด Q โ€” Fetch API `[Advanced]` **๐ŸŽด FRONT** ``` fetch(url) .then(โ†’ parse) .then(โ†’ use data) ``` ```js fetch('data/deck.json') .then(r => r.json()) .then(cards => { console.log(cards.length); }); ``` **๐Ÿ”„ BACK** *The JS version of Python's `requests`. Pull data from anywhere without reloading the page.* ๐ŸŽฏ Try it: Fetch `dojo_52_card_deck.json` locally. Log the first card's `concept` field to the console. *** ### ๐Ÿ”ด K โ€” console.log() & Debugging `[Advanced]` **๐ŸŽด FRONT** ``` console.log() โ†’ F12 console console.error() โ†’ red alert console.table() โ†’ formatted ``` ```js const player = {name:'David', honor:90}; console.log('Player:', player); console.table(player); ``` **๐Ÿ”„ BACK** *The print() of JavaScript. Your debug voice in the browser. Master this before anything else.* ๐ŸŽฏ Try it: Open any webpage. In the console, type `console.table(document.querySelectorAll('a'))`. What do you see? *** ## GITHUB WORKFLOWS ๐Ÿ”€ **Suit of Chronicle.** Git tracks every change. History is accountability. | Tier | Cards | Learning Outcome | |------|-------|------------------| | ๐ŸŸข Beginner | Aโ€“7 | clone, init, status, add, commit, push, pull | | ๐ŸŸก Intermediate | 8โ€“J | Branching, merging, pull requests, README | | ๐Ÿ”ด Advanced | Qโ€“K | GitHub Actions, stable IDs in save files | *** ### ๐ŸŸข A โ€” git clone `[Beginner]` **๐ŸŽด FRONT** ``` โ˜๏ธ GitHub repo โ†“ git clone ๐Ÿ’ป local copy ``` ```python git clone https://github.com/user/repo.git cd repo ls ``` **๐Ÿ”„ BACK** *Your first step onto the mat. Downloads the dojo to your machine.* ๐ŸŽฏ Try it: Clone the Dojo Ascension repo. Navigate into it. Run `ls` and name 3 files you see. *** ### ๐ŸŸข 2 โ€” git init `[Beginner]` **๐ŸŽด FRONT** ``` ๐Ÿ“ my-project/ git init โ†“ ๐Ÿ“ .git/ created ``` ```python mkdir my-dojo cd my-dojo git init ls -a ``` **๐Ÿ”„ BACK** *Founding a new dojo. The .git folder is your chronicle's spine.* ๐ŸŽฏ Try it: Create a new folder, run `git init`, then `ls -a`. Can you see the hidden `.git` directory? *** ### ๐ŸŸข 3 โ€” git status `[Beginner]` **๐ŸŽด FRONT** ``` ๐Ÿ”ด untracked ๐ŸŸก staged ๐ŸŸข committed ``` ```python git status # On branch main # Untracked files: # new_file.py ``` **๐Ÿ”„ BACK** *Know your terrain before you move. A scout always checks the field first.* ๐ŸŽฏ Try it: Create a new file in your repo. Run `git status`. What color/label does it show? *** ### ๐ŸŸข 4 โ€” git add `[Beginner]` **๐ŸŽด FRONT** ``` untracked file git add . โ†“ staged (ready) ``` ```python git add . # stage everything git add README.md # stage one file git status # confirm ``` **๐Ÿ”„ BACK** *Prepare your offering before committing. Stage is your review moment.* ๐ŸŽฏ Try it: Stage only ONE specific file (not `.`). Run `git status`. Notice the difference. *** ### ๐ŸŸข 5 โ€” git commit `[Beginner]` **๐ŸŽด FRONT** ``` ๐Ÿ“ธ snapshot git commit -m "message" โ†“ logged to history ``` ```python git commit -m "feat: add card deck module" # present tense, imperative mood # "add" not "added" ``` **๐Ÿ”„ BACK** *The journalist's dateline. A commit is a timestamped, immutable statement of fact.* ๐ŸŽฏ Try it: Commit your staged file. Then run `git log --oneline`. Read your own history. *** ### ๐ŸŸข 6 โ€” git push `[Beginner]` **๐ŸŽด FRONT** ``` ๐Ÿ’ป local commits git push โ†“ โ˜๏ธ GitHub updated ``` ```python git push origin main git push # if upstream set ``` **๐Ÿ”„ BACK** *Publishing your chronicle. Your work becomes visible to the community.* ๐ŸŽฏ Try it: Push a commit to GitHub. Refresh the repo page. Can you see your commit message online? *** ### ๐ŸŸข 7 โ€” git pull `[Beginner]` **๐ŸŽด FRONT** ``` โ˜๏ธ new commits exist git pull โ†“ ๐Ÿ’ป local updated ``` ```python git pull # fetch + merge git pull origin main ``` **๐Ÿ”„ BACK** *Staying current with the community. A practitioner keeps their knowledge fresh.* ๐ŸŽฏ Try it: Make a change on GitHub.com directly. Then pull it to your local machine. Confirm the change appeared. *** ### ๐ŸŸก 8 โ€” Branch `[Intermediate]` **๐ŸŽด FRONT** ``` main โ”€โ”€โ”€โ—โ”€โ”€โ”€โ—โ”€โ”€โ”€โ— \ โ—โ”€โ”€โ”€โ— feature ``` ```python git checkout -b feature/card-deck git branch # list branches git switch main # return to main ``` **๐Ÿ”„ BACK** *R&D in isolation. Wing Chun: deflect risk to a side branch so experiments can't crash production.* ๐ŸŽฏ Try it: Create a branch named `feature/your-name`. Add a file. Commit it. Switch back to main. Is the file still there? *** ### ๐ŸŸก 9 โ€” Merge `[Intermediate]` **๐ŸŽด FRONT** ``` main โ”€โ”€โ”€โ—โ”€โ”€โ”€โ—โ”€โ”€โ”€โ—โ”€โ”€โ— \ / โ—โ”€โ”€โ”€โ— ``` ```python git switch main git merge feature/card-deck git log --oneline --graph ``` **๐Ÿ”„ BACK** *Returning force to the trunk. Wing Chun redirect โ€” the experiment rejoins the main flow.* ๐ŸŽฏ Try it: Merge your feature branch into main. Run `git log --oneline --graph`. Draw what you see. *** ### ๐ŸŸก 10 โ€” Pull Request `[Intermediate]` **๐ŸŽด FRONT** ``` feature branch โ†’ ๐Ÿ” PR opened โ†’ ๐Ÿ‘ฅ reviewed โ†’ โœ… merged to main ``` ```python # On GitHub: # 1. Push branch # 2. Open Pull Request # 3. Add description # 4. Request review # 5. Merge ``` **๐Ÿ”„ BACK** *The digital tatami mat. Code review is mutual growth โ€” the gentle art of making each other better.* ๐ŸŽฏ Try it: Push a feature branch to GitHub. Open a PR. Write a 2-sentence description of what it changes and why. *** ### ๐ŸŸก J โ€” README.md `[Intermediate]` **๐ŸŽด FRONT** ``` # Project Title ## What it does ## How to run ## How to contribute ``` ```python # Dojo Ascension A terminal learning game. ## Run ```bash python dojo.py ``` ## Contributing See CONTRIBUTING.md ``` **๐Ÿ”„ BACK** *A project's front door. The README is the first impression โ€” make it welcoming.* ๐ŸŽฏ Try it: Write a one-paragraph README for a project you're working on. Include 'What it does' and 'How to run'. *** ### ๐Ÿ”ด Q โ€” GitHub Actions `[Advanced]` **๐ŸŽด FRONT** ``` on: push jobs: test: โ†’ auto-runs โ†’ โœ… or โŒ ``` ```python # .github/workflows/test.yml on: [push] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - run: python -m pytest ``` **๐Ÿ”„ BACK** *Your automated sensei. Tests run on every push without you asking โ€” the dojo that never sleeps.* ๐ŸŽฏ Try it: Add a GitHub Actions workflow that runs `echo 'Dojo tests passing'` on every push. Watch it succeed. *** ### ๐Ÿ”ด K โ€” Stable ID in Save `[Advanced]` **๐ŸŽด FRONT** ``` โœ… completed: ['git-clone', 'py-vars'] โŒ completed: [1, 3, 5] IDs survive refactoring. Numbers don't. ``` ```python # save.json { "player": "David", "rank": "Adept", "completed": [ "git-clone", "py-variables", "json-objects" ] } ``` **๐Ÿ”„ BACK** *Names outlive positions. The dojo chronicle references practitioners by name, not row number.* ๐ŸŽฏ Try it: Open your save file. If it uses numbers, refactor to string IDs. Commit the change with a clear message explaining why. *** ## Rank Unlock Requirements | Rank | Gate | |------|------| | Initiate | Identify 5 cards by concept in any suit | | Apprentice | Master all ๐ŸŸข Beginner cards (Aceโ€“6 or 7) in Python | | Practitioner | Master all ๐ŸŸข Beginner cards across all 4 suits | | Adept | Master all ๐ŸŸก Intermediate cards in at least 2 suits | | Expert | Master all 52 cards | | Co-Architect | Teach any 10 cards to another learner. Explain the exercise, not just the answer. | *** ## Integration with `missions.json` & Save File Each card has a stable `id` field. Reference it in your save file: ```json { "player": "David", "rank": "Practitioner", "mastered_cards": [ "card-python-a", "card-python-2", "card-json-a", "card-git-4" ] } ``` > โšก Build `card_deck.py` as a separate module that loads `dojo_52_card_deck_v2.json` โ€” keep content out of the engine, so educators can add cards without touching Python. ===== CHANGELOG.md ===== # Changelog ## 2026-07 ### Metrics | Metric | Previous | Current | Notes | |---|---:|---:|---| | Active contributors (merged PR authors) | N/A | 1 | Baseline month after governance + onboarding rollout | | Merged mission/content PRs | N/A | 1 | Baseline content/process bundle merged | | 14-day learner return rate | N/A | Collecting | Use `measure_retention.py` during first cohort | | Median missions completed per active learner | N/A | Collecting | Use `export_learner_data.py` during first cohort | ### Shipped - Added funding-readiness docs, governance baseline, and contribution templates. - Added privacy + impact measurement docs and cohort data export utilities. - Added learner momentum, pathway, and retention-tracking support to terminal and browser flows. ### Learned - Funders and pilot partners need concrete reporting pathways, not only aspirational metrics. ### Next experiment - Run one pilot cohort and collect structured feedback. - Publish a short evidence bundle with retention, completion, confidence, and contributor signals. ===== CONTEXT.md ===== # CONTEXT: AI and Contributor Onboarding Dojo Ascension is a lightweight, mission-driven learning project designed to teach Python, Git, JSON, and code review through practical prompts and reflection. The repository is intentionally structured so that non-programmers, educators, and engineers can collaborate on a shared curriculum without needing a heavy platform or external services. At a high level, there are two active frontends that share the same pedagogical model: a terminal experience in `dojo_classroom.py` and a browser experience in `dojo_web.html`. The terminal path includes optional reflective journaling and local JSON persistence for progress tracking. Core mission data is stored in `missions/missions.json`, with companion mission files in `missions/` for structured, schema-rich authoring workflows. If you are trying to understand the project quickly, read in this order: 1. `README.md` for project intent, workflows, and contributor entry points. 2. `missions/missions.json` for the canonical mission sequence consumed by the runtime. 3. `CONTRIBUTING.md` and `missions/MISSION_REVIEW_RUBRIC.md` for writing standards. 4. `docs/` materials for governance, metrics, privacy, and accessibility constraints. Continuous integration currently validates quality with tests and mission validation. Existing checks live in `.github/workflows/validate.yml` and run unit tests plus mission schema checks on pushes and pull requests. A dedicated workflow, `.github/workflows/refresh-repo-bundle.yml`, now keeps `repo_bundle.txt` refreshed on pushes to `main`, using only `GITHUB_TOKEN` for bot-authenticated commits when bundle content changes. The plain-text bundle pipeline is maintained by `repo_text_export.py` and `refresh_repo_bundle.py`. The exporter intentionally excludes generated bundle artifacts to avoid recursive self-ingestion and unnecessary churn. This keeps AI-facing snapshots stable and useful for rapid analysis. To verify local changes, run: - `python -m unittest discover -s tests` - `python validate_missions.py` - `python refresh_repo_bundle.py` Project tone matters as much as syntax. Preferred contributions are clear, inclusive, and practical. The style favors plain language, respectful review, and system-level thinking over gatekeeping or jargon-heavy explanations. Keep changes focused, preserve interoperability between frontends, and avoid introducing hidden infrastructure dependencies. For AI tools and fast repository ingestion, use `llms.txt` and `repo_bundle.txt` as the first loading surfaces, then drill into specific files relevant to the task. ===== CONTRIBUTING.md ===== # ๐ŸŒฑ Contributing to Dojo Ascension Welcome, fellow cultivator of code and community. Whether you're a beginner learning Python or an experienced developer, your contributions help grow this learning ecosystem. **Dojo Ascension v5.0** is built on the belief that code is not syntaxโ€”it's a framework for thinking about systems. We welcome contributions that expand the curriculum, improve the engine, and strengthen the community. --- ## โšก One-Command Contributor Onboarding Use these exact commands: ```bash python newcomer.py python newcomer.py --play ``` - `python newcomer.py` installs dependencies, runs tests, and validates missions. - `python newcomer.py --play` does the same and then launches the game. --- ## ๐ŸŽฏ How You Can Contribute ### 1. **Add a Mission** (Easiest!) Non-programmers and subject-matter experts are welcome here. If you understand a concept deeplyโ€”whether it's Git, web scraping, data analysis, genealogy research, or journalismโ€”you can contribute a mission. **Mission writers do NOT need to know Python.** You only need to: - Understand your topic - Write it clearly - Follow the JSON mission format (see below) - Test it locally (one command) ### 2. **Improve Existing Missions** - Fix typos, clarify explanations - Add hints or better analogies - Adjust honor rewards based on difficulty - Propose reflective questions ### 3. **Code Contributions** - Bug fixes - Performance improvements - New features (e.g., offline mode, dashboard enhancements) - Testing and quality assurance ### 4. **Documentation & Translation** - Improve README, guides, examples - Translate missions into other languages - Create video walkthroughs - Write blog posts about the Dojo philosophy ### 5. **Facilitation, Accessibility, and Cohort Support** - Run a small pilot cohort in a library, classroom, or community lab - Review learner prompts for clarity and accessibility - Capture pilot feedback with the repository templates - Help summarize outcomes in `CHANGELOG.md` ## ๐Ÿงญ Contributor Pathways Choose the track that fits your confidence and experience: - **First-time coder** โ€” run `python newcomer.py`, complete 1 mission, then fix one small issue - **Mission writer / educator** โ€” start with `missions/mission_template.json`, then use the mission rubric - **Accessibility reviewer** โ€” use `docs/ACCESSIBILITY_BASELINE.md` and `docs/ACCESSIBILITY_TESTING.md` - **Translator / editor** โ€” improve readability, translation readiness, and learner prompts - **Organizer / facilitator** โ€” run a cohort, gather feedback, and publish a short pilot summary See [ROADMAP.md](ROADMAP.md) for the current โ€œnow / next / laterโ€ priorities. --- ## ๐Ÿ“ How to Add a Mission ### The Mission JSON Format All missions live in `missions.json`. To add a new mission, add a JSON object to the `"missions"` array. Start from [`missions/mission_template.json`](missions/mission_template.json), then review with [`missions/MISSION_REVIEW_RUBRIC.md`](missions/MISSION_REVIEW_RUBRIC.md). #### Minimal Example ```json { "id": "git_remote_collaboration", "number": 11, "title": "Remote Collaboration with Git", "philosophy": "Your philosophy anchor here.", "economics": "Your economic parallel here.", "tech_concept": "The actual technical explanation.", "challenge": "The question or task prompt.", "answer": "git push origin main", "skill": "git", "honor_base": 20 } ``` #### Full Schema Documentation | Field | Type | Required | Description | |-------|------|----------|-------------| | `id` | string | โœ… Yes | **Stable identifier** โ€” never changes. Format: `lowercase_with_underscores`. Example: `python_list_comprehension`, `json_parsing_api_response` | | `number` | integer | โœ… Yes | Sequence number in curriculum. Can be 1-100+. If you add mission 11, use `11`. | | `title` | string | โœ… Yes | Mission title. Keep it under 50 characters. Example: `"APIs & HTTP"` | | `philosophy` | string | โœ… Yes | Cross-disciplinary anchor (martial arts, theatre, economics, activism, history). 2-4 sentences. This is where you connect code to the human world. | | `economics` | string | โœ… Yes | How this concept relates to resource management, infrastructure, governance, or markets. 2-3 sentences. | | `tech_concept` | string | โœ… Yes | The actual technical explanation without jargon. Show a brief code example if helpful. | | `challenge` | string | โœ… Yes | The question or prompt the learner will answer. Can be a question, instruction, or scenario. | | `answer` | string | โœ… Yes | The expected answer (case-insensitive, whitespace-flexible). Can be partial match (e.g., user types "def" and we check if "def" is in their input). | | `skill` | string | โœ… Yes | Domain this teaches: `python`, `git`, `json`, `architecture`, `review`, or another lowercase domain name | | `honor_base` | integer | โœ… Yes | Base honor points (typically 15-30). Harder missions earn more. | | `hint` | string | โš ๏ธ Optional | A helpful hint revealed after 3 failed attempts. | | `learning_level` | string | โš ๏ธ Optional | Cognitive complexity: `recall`, `application`, `analysis`, or `reflection`. Helps learners understand progression. | #### Real Examples from the Core Curriculum **Example 1: Simple Recall** ```json { "id": "git_system_grounding", "number": 1, "title": "System Grounding", "philosophy": "Like Tai Chi, where you must feel the ground before moving, a programmer must understand their environment. 'Wu Wei' is achieved when your tools become extensions of your mind.", "economics": "Infrastructure is the basis of all macro-economic stability. Before building goods, we build roads. Here, your terminal and shell are your economic infrastructure.", "tech_concept": "The terminal. Git is our tool for interacting with the open-source global supply chain of ideas.", "challenge": "Type the command to clone a git repository.", "answer": "git clone", "skill": "git", "honor_base": 20 } ``` **Example 2: Multiple Acceptable Answers** ```json { "id": "python_variables", "number": 2, "title": "Variables & Data Lineage", "philosophy": "Genealogy teaches us that tracing lineage reveals identity. A variable is a named vessel; its lineage of data determines its behavior in the ecosystem.", "economics": "Micro-economics dictates that resources are scarce. Variables allocate memoryโ€”our most precious digital resource. Good naming prevents systemic inflation.", "tech_concept": "In Python, we assign values using the '=' operator. E.g., `water_supply = 100`.", "challenge": "Write exactly this: assign the integer 42 to a variable named 'seed'.", "answer": "seed = 42", "skill": "python", "honor_base": 15, "hint": "Format: variable_name = value" } ``` --- ## โœ๏ธ Writing Your Mission ### Philosophy Anchor (the heart of the mission) Connect the code to something meaningful. Examples: - **Martial Arts**: Wu Wei (effortlessness), Jeet Kune Do (efficiency), BJJ (collaboration) - **History/Politics**: Estonia's e-governance, Cornel West's justice, activism and accountability - **Theatre**: Uta Hagen's acting techniques, roles and characters, improvisation - **Economics**: Resource scarcity, infrastructure, markets, supply chains - **Your field**: Journalism, genealogy, biology, music, climate scienceโ€”bring your expertise **Why?** Learners remember code better when it connects to ideas they already care about. ### Economics Parallel How does this concept relate to real-world resource management or infrastructure? Examples: - **Variables** โ†’ "Resources are scarce; memory allocation prevents inflation" - **Git Commits** โ†’ "Ledgers hold institutions accountable; immutability prevents corruption" - **Functions** โ†’ "Factories take raw inputs and produce finished goods; reuse prevents waste" - **APIs** โ†’ "Open data democratizes power; closed APIs create monopolies" **Why?** This frames code as part of larger systemsโ€”not isolated syntax. ### Technical Concept Keep it simple. Use plain language. If you use code examples, make them brief. Don't assume learners know advanced topics yet. ### Challenge Write a clear, unambiguous prompt. Can be: - A question: `"What keyword is used to define a function?"` - An instruction: `"Write a print() statement that outputs your name."` - A scenario: `"You are a Git maintainer. What command isolates your experimental branch?"` ### Answer Keep it short. The engine checks for **substring match** (case-insensitive). So: - `"answer": "def"` matches "def", "DEF", "def is the keyword", "define using def" - `"answer": "git clone"` matches "git clone", "git clone my-repo", "GIT CLONE" If the answer needs to be exact (e.g., `seed = 42`), note that in the challenge itself: *"Write exactly this..."* ### Hint Optional but powerful. Hints appear after 3 failed attempts. They should: - Point toward the concept, not give the answer - Examples: - Challenge: "What does JSON stand for?" Hint: "Think of the initials: J_S_O_N" - Challenge: "Create a list with 3 numbers" Hint: "Use square brackets: [1, 2, 3]" --- ## ๐Ÿงช Testing Your Mission Locally 1. **Edit `missions.json`** โ€” Add your mission object to the `"missions"` array. ### Shared Workstation Reminder If you are testing on a shared workstation, give each person their own data directory so progress files do not collide: ```powershell $env:DOJO_DATA_DIR = "$HOME\.dojo-ascension\your-name" python dojo_classroom.py ``` If you use GitHub Desktop, keep each account's clone in a separate folder and sign in with the intended account before pushing. 2. **Validate your missions:** ```bash python validate_missions.py ``` 3. **Run the Dojo:** ```bash python dojo_classroom.py ``` 3. **Navigate to your mission:** - Choose option 2 (Choose Specific Mission) - Enter your mission number (e.g., 11) 4. **Test the challenge:** - Try the correct answer - Try wrong answers and verify the error message - Verify the hint appears after 3 failed attempts 5. **Check the save file:** ```bash cat ~/.dojo_save.json ``` - Confirm your mission ID appears in `"completed"` - Confirm honor points were awarded ### Pilot-friendly reporting commands ```bash python export_learner_data.py --data-dir ~/.dojo_ascension --format json python measure_retention.py --data-dir ~/.dojo_ascension ``` Use these during cohorts to produce anonymized baseline metrics for `CHANGELOG.md` and pilot summaries. --- ## โœ… PR Quality Baseline (Required) All PRs must pass: ```bash python -m unittest discover -s tests python validate_missions.py ``` Mission/content PRs must complete the Definition of Done checklist in [`.github/pull_request_template.md`](.github/pull_request_template.md). --- ## ๐ŸŒ Accessibility & Inclusion Guardrails Use [`docs/ACCESSIBILITY_BASELINE.md`](docs/ACCESSIBILITY_BASELINE.md) as the baseline for: - Plain language and low-jargon writing - Inclusive examples and learner-friendly prompts - Lightweight web checks (keyboard flow, contrast, readable layout) --- ## ๐Ÿฃ Good First Mission Pathway (Non-Coders) 1. Copy [`missions/mission_template.json`](missions/mission_template.json) 2. Fill one mission with a familiar topic 3. Keep challenge + answer short and testable 4. Add one hint 5. Self-review with [`missions/MISSION_REVIEW_RUBRIC.md`](missions/MISSION_REVIEW_RUBRIC.md) 6. Run `python newcomer.py` 7. Open a PR and request mission/content review --- ## ๐Ÿค Proposing a Mission Pack Want to create a whole collection? Examples: **Journalism Pack** (`missions_journalism.json`) - Web scraping basics - Data validation - Ethical APIs - Fact-checking with code **Genealogy Pack** (`missions_genealogy.json`) - JSON for family trees - CSV parsing - Recursive algorithms (descendants) - Data privacy & consent **Governance Pack** (`missions_governance.json`) - Public data APIs - Open-source contribution workflows - Policy simulation in code - Transparency through logs To propose a pack: 1. Open an issue with the title: `Pack Proposal: [Your Pack Name]` 2. List the missions you want to write 3. Describe why this domain matters to learners 4. We'll discuss scope and integration --- ## ๐ŸŽฏ Contribution Workflow ### Fork & Clone ```bash git clone https://github.com/YOUR-USERNAME/dojo-ascension.git cd dojo-ascension ``` ### Create a Branch ```bash git checkout -b feature/add-mission-webscraping # Or for a mission pack: git checkout -b feature/pack-journalism ``` ### Make Your Changes - Edit `missions.json` (or create `missions_[pack-name].json` for a full pack) - Test locally - Update README if needed ### Commit ```bash git commit -m "feat: add webscraping mission - New mission: Web Scraping 101 (BeautifulSoup basics) - Covers: HTML parsing, API requests, ethical considerations - Skill: architecture (python+web) - Honor: 25 points Tested locally and verified save/load works correctly." ``` ### Push & Open PR ```bash git push origin feature/add-mission-webscraping ``` Then open a PR on GitHub. Use this template: ```markdown ## What This PR Adds [Brief description of your mission(s)] ## Missions Included - [ ] Mission 11: Web Scraping 101 - (Add more with checkboxes) ## Testing - [x] Tested locally with `python dojo_classroom.py` - [x] Verified mission appears in menu - [x] Verified correct answer is accepted - [x] Verified save file updates - [x] Verified hint system works (if applicable) ## Links - Related issue (if any): #XX - Philosophy reference: [Your source] - Economic parallel: [Your source] ``` --- ## ๐Ÿง  Tips for Great Missions ### 1. **Test Rigorously** - Does the engine parse your JSON? - Does the challenge make sense? - Are there edge cases in the answer checking? ### 2. **Honor Difficulty** - Simple recall (definitions): 15-20 honor - Application (write code): 20-25 honor - Analysis (explain why): 25-30 honor - Reflection (systems thinking): 30-35 honor ### 3. **Connect Broadly** Don't assume learners know much about your field. Explain why it matters: - "Genealogy teaches..." โ†’ relates to variables and data lineage - "Journalism teaches..." โ†’ relates to Git history and accountability - "Martial arts teaches..." โ†’ relates to efficiency and discipline in code ### 4. **Avoid Gatekeeping** - Don't assume advanced knowledge - Provide context in `tech_concept` - Make hints genuinely helpful - Remember: your learner might be 14 or 74, from any background ### 5. **Be Precise with Answers** - Substring matching is flexible (good for "contains" checks) - For exact matches, note it in the challenge: *"Write exactly..."* - Test with whitespace variations: `seed = 42`, `seed=42`, `seed = 42 ` --- ## ๐ŸŽ“ Philosophy & Tone This project values: - **Clarity over jargon** โ€” Explain hard concepts in plain language - **Breadth over depth** โ€” Touch many disciplines, not just CS - **Practice over perfection** โ€” Celebrate returning, not just winning - **Community over ego** โ€” Assume good intent, offer constructive feedback When writing a mission, ask yourself: - *Does this connect to something real?* - *Could a non-programmer understand the analogy?* - *Does this teach mastery, not just answers?* - *Would I want to learn from this?* --- ## ๐Ÿ› Reporting Issues Found a bug or have feedback? Open an issue: 1. **For mission content:** "Mission X has unclear wording" โ†’ Suggest improvement 2. **For the engine:** "Save/load fails on Windows" โ†’ Provide error message & environment 3. **For docs:** "README doesn't mention X" โ†’ Suggest what should be added ### Good Issue Title - โœ… "Mission 3 answer validation too strict" - โœ… "dojo_classroom.py fails if ~/.dojo_save.json corrupted" - โŒ "bug" or "help" --- ## ๐Ÿ™ Code of Conduct We're building a learning community. Be kind: - **Assume good intent** in reviews and feedback - **Ask questions** instead of making demands - **Celebrate learning**, not just expertise - **Welcome all backgrounds** โ€” your unique perspective is valuable --- ## ๐Ÿ“š Resources - **Python Learning:** [Official Python Docs](https://docs.python.org/) - **Git Learning:** [Git Book](https://git-scm.com/book) - **Philosophy References:** - Bruce Lee on efficiency: *"Absorb what is useful..."* - Cornel West on justice: *"Justice is what love looks like in public"* - Uta Hagen on acting: *"Nine Questions"* (foundation of reflection journal) - **Educational Design:** See `README.md` for pedagogical model --- ## ๐Ÿš€ Ready to Contribute? 1. Pick a topic you know and care about 2. Write your mission following the JSON format 3. Test it locally 4. Open a PR with a clear description 5. Engage with feedback 6. Merge and celebrate! ๐ŸŽ‰ **Questions?** Open an issue, start a discussion, or reach out to maintainers. **Let us never stop learning from Galileo.** โ€” SolarPunk HackNet ===== CONTRIBUTORS.md ===== # Contributors Dojo Ascension exists because people gave their time, words, and code. Thank you. We recognize every kind of contribution โ€” code, missions, translations, accessibility, documentation, facilitation, and review. If you contributed and are not listed here, please open a pull request adding yourself, or open an issue and a maintainer will add you. ## Code & Content Contributors Listed by first contribution date (derived from git history). | Contributor | First contribution | |---|---| | [@ZagreusDaViDalucarD](https://github.com/ZagreusDaViDalucarD) | 2026-01-05 | | [@alucardzagreus-boop](https://github.com/alucardzagreus-boop) | 2026-06-22 | | [@zagreusalucard-ctrl](https://github.com/zagreusalucard-ctrl) | 2026-06-23 | | [@g0ldenj1nu](https://github.com/g0ldenj1nu) | 2026-06-23 | ## Tools & Automation - **GitHub Copilot coding agent** โ€” assisted with scoped code and documentation tasks under human review. --- *Want to see your name here? See [CONTRIBUTING.md](CONTRIBUTING.md) for pathways: mission writer, translator, facilitator, accessibility reviewer, and code contributor. Every merged pull request counts.* ===== FUNDING_ONE_PAGER.md ===== # Dojo Ascension โ€” One-Page Mission & Funding Story ## Public promise Dojo Ascension helps beginners, artists, educators, libraries, community labs, and small nonprofits teach practical digital skills through missions that connect code to social and ecological responsibility. Learners gain: - Basic fluency in Python, Git, JSON, and systems thinking - A habit of reflective practice, not just task completion - A contribution path into open source, even without formal CS training Organizations gain: - A lightweight digital-literacy system that works in browsers or Python terminals - Reusable curriculum and learner-state formats without buying a heavyweight LMS - A contributor pathway that supports educators, writers, reviewers, translators, and facilitators - Local-first deployment with minimal infrastructure, low dependency risk, and low operating cost Why this is uniquely solarpunk: - Teaches technology as stewardship, transparency, and community infrastructure - Frames coding as a civic and creative tool, not only a job pipeline - Welcomes interdisciplinary mission authors (journalism, governance, genealogy, climate, arts) ## Measurable outcomes (public) 1. **Learner retention** - Metric: % of learners who return within 14 days after first mission - Initial target: 40%+ 2. **Mission completion** - Metric: median number of completed missions per active learner per month - Initial target: 3+ 3. **Contributor growth** - Metric: number of unique contributors with merged PRs each month - Initial target: 5+ contributors/month ## What funding supports first - Core maintenance and bug fixing - Curriculum and mission pack expansion - Accessibility and localization improvements - Community operations (reviews, pilot facilitation, monthly reporting) - Privacy-preserving impact measurement and pilot evidence collection ## Efficiency story for adopters - No database or paid SaaS is required for core use - Browser and terminal clients reuse the same curriculum model - Mission content can be expanded by non-programmers using JSON templates - Local learner saves make classroom pilots possible in low-connectivity environments - Small teams can facilitate cohorts without building custom onboarding infrastructure ## Accountability loop Each month, maintainers publish a short update with: - Progress on the 3 measurable outcomes - What shipped, what was learned, and what changed next - Risks or blockers that need support ===== Makefile ===== .PHONY: setup play test validate check newcomer setup: python -m pip install --upgrade pip python -m pip install -r requirements.txt play: python dojo_classroom.py test: python -m unittest discover -s tests validate: python validate_missions.py check: test validate newcomer: setup check @echo "โœ… Ready. Run 'make play' or 'python newcomer.py --play' to start the dojo." ===== README.md ===== # ๐Ÿฅ‹ Dojo Ascension: A Digital Ecology for Creators **Learn Python, Git, and systems thinking through play, mentorship, and ecology โ€” no prior experience required.** [![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0) [![Python 3.8+](https://img.shields.io/badge/python-3.8%2B-brightgreen.svg)](https://www.python.org/downloads/) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](CONTRIBUTING.md) [![Good First Issues](https://img.shields.io/badge/good%20first%20issues-open-purple.svg)](https://github.com/solarpunkopensourcelaboratory/dojo-ascension/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) [![Contributors](https://img.shields.io/badge/contributors-welcome-brightgreen.svg)](https://github.com/solarpunkopensourcelaboratory/dojo-ascension/graphs/contributors) **New here?** โ†’ [Start a good first issue](https://github.com/solarpunkopensourcelaboratory/dojo-ascension/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) ยท [Read the Contributing Guide](CONTRIBUTING.md) ยท [Open in a Codespace](https://github.com/solarpunkopensourcelaboratory/dojo-ascension) (zero local setup) > *"The world is already at your fingertips. You are here to find your voice."* Welcome to the Dojo. This is not just a codebase or a traditional programming class. This is a sanctuary for the curious, the artists, the philosophers, and the inner child eager to understand how the digital world works. Whether you are a seasoned traveler recovering from a difficult learning journey, or someone stepping into a terminal for the very first time, you belong here. We do not learn code to feed the machine. We learn code so that you can apply these instruments to your **own** arts, hobbies, and passions. Here, you will learn Python, Linux, and systems thinking through play, mentorship, and ecology. Your wings are yours to open. Let us provide the updraft. --- ## ๐Ÿ›ค๏ธ The 10-Minute Spark: Start Here No prior experience is required. Follow these three steps to step onto the mat and begin your first mission. ### Step 1: Claim Your Space (Clone the Dojo) Open your computer's terminal and copy-paste this command to bring the Dojo to your local machine: ```bash git clone https://github.com/solarpunkopensourcelaboratory/dojo-ascension.git cd dojo-ascension ``` ### Step 2: Equip Your Gear (Install Requirements) Every explorer needs the right tools. Run this command to unpack your gear securely: ```bash pip install -r requirements.txt ``` ### Step 3: Step Onto the Mat (Enter the Classroom) You are ready. Awaken the engine and begin your journey: ```bash python dojo_classroom.py ``` *(When the screen lights up and the Dojo welcomes you, take a breath. You have just taken your first step into a larger world.)* --- ## ๐ŸŒฑ Our Superobjective Inspired by the greatest mentorsโ€”from the theatre stage to the realms of animationโ€”our ultimate goal is to raise the next generation of formidable, responsible, and informed creators. We believe the strongest future is built by humans and machines intelligent enough to choose what they want, in harmony with the planet that sustains us. --- ## ๐ŸŒป Mission & Funding Snapshot See the one-page public promise and measurable outcomes in [FUNDING_ONE_PAGER.md](FUNDING_ONE_PAGER.md). Key outcomes we track: - Learner retention (14-day return rate) - Mission completion (median missions completed per active learner) - Contributor growth (unique merged PR authors per month) Why organizations adopt it: - Lightweight browser + terminal delivery - Shared mission schema instead of a heavyweight LMS rebuild - Local-first learner data for low-resource environments - Contributor pathways for educators, writers, reviewers, translators, and facilitators --- ## Frontends and Clients The Dojo Ascension repo defines a data and logic layer for a learning game: - **Missions are stored as JSON** (`missions.json` and `missions/*.json`) - **Player progress and journal entries are stored as JSON** (save files) - **Multiple frontends available**: - Terminal / VS Code experience written in Python (`dojo_classroom.py`) - Browser-based single-page app (`dojo_web.html`) โ€” **NEW!** We explicitly invite other frontends (RPG, mobile, desktop, etc.) that: - Consume the same mission schema and skill/honor model - Preserve the educational intent of each mission - Respect the project license (see LICENSE) If you are building a new frontend, open an issue to coordinate on data formats and progression so we stay interoperable. --- *Use in fundraisers and ethical businesses* We explicitly welcome: - Nonโ€‘profits using Dojoโ€‘derived games in fundraisers - Ethical solarpunk businesses building commercial games or tools that teach with Dojo missions - If you distribute software that incorporates Dojo code, you must follow the GPLโ€‘3.0 license (keep derivative code open-source, provide source to users) - If you want to discuss special arrangements or dual-licensing for a specific project, open an issue or contact the maintainers --- ## ๐Ÿซ Organizational Efficiency & Public Value Dojo Ascension is designed as **mission-aligned digital literacy infrastructure**: - **Low overhead** โ€” no database or paid SaaS required for core use - **Low resource** โ€” browser edition works offline; terminal edition runs on basic Python setups - **Reusable curriculum** โ€” JSON missions can be expanded without a large engineering team - **Interoperable** โ€” multiple frontends can share the same learner and mission model - **Local-first** โ€” institutions can pilot cohorts without sending learner data to a third-party service This makes the project useful for: - libraries - classrooms - community technology labs - mutual-aid networks - small nonprofits and fellowships --- ## ๐Ÿš€ Quick Start ### Option A: Play in Your Browser (Easiest) No installation required. Just download and open: ```bash # Clone the repo git clone https://github.com/solarpunkopensourcelaboratory/dojo-ascension.git cd dojo-ascension # Open in your browser open dojo_web.html # or double-click dojo_web.html in your file explorer ``` **Features:** - โœ… All 10 missions available - โœ… Progress saves to browser storage (persists across sessions) - โœ… Works offline - โœ… Mobile responsive - โœ… Zero dependencies --- ### Option B: Play in Terminal (Python) For a deeper, reflective experience with optional journal entries: **Prerequisites:** - Python 3.8+ - Git - 2GB free disk space **Installation:** ```bash # 1. Clone this repo git clone https://github.com/solarpunkopensourcelaboratory/dojo-ascension cd dojo-ascension # 2. Install dependencies pip install -r requirements.txt # 3. Launch the Dojo python dojo_classroom.py ``` **First Time Setup:** When you run the game, you'll be prompted to enter your name. The system will: 1. Check your environment (Python version, Git installation, disk space) 2. Create a save file at `~/.dojo_save.json` 3. Optionally create a journal at `~/.dojo_journal_data.json` **Shared Workstation Tip:** On a shared machine, keep each participant's progress separate: ```bash export DOJO_DATA_DIR="$HOME/.dojo-ascension/alice" python dojo_classroom.py ``` --- ## ๐Ÿ“– Curriculum (10 Core Missions) | # | Mission | Skill | Philosophy | |---|---------|-------|------------| | 1 | System Grounding | Git | Wu Wei: Your tools become extensions of your mind | | 2 | Variables & Data Lineage | Python | Genealogy: Tracing lineage reveals identity | | 3 | JSON โ€” Data Language | JSON | Cornel West: Justice is cooperation in code | | 4 | Functions & Jeet Kune Do | Python | Bruce Lee: Maximum efficiency with minimum effort | | 5 | Git โ€” Journalistic Integrity | Git | Activism: Git is an immutable ledger of truth | | 6 | Git โ€” Branching & Merging | Git | Wing Chun: Deflect and redirect, don't oppose | | 7 | APIs & Digital Journalism | Architecture | Freedom of Information: Query the source directly | | 8 | File I/O โ€” Institutional Memory | Python | Bushido: Legacy ensures society can learn | | 9 | OOP โ€” Sociological Modeling | Architecture | Theatre: Roles and Actors in a cooperative | | 10 | Code Review โ€” Gentle Art | Review | BJJ: Testing each other's code before production | --- ## ๐ŸŽฎ How to Play ### Main Menu Options 1. **Start Next Mission** โ€” Play the next incomplete mission in sequence 2. **Choose Specific Mission** โ€” Jump to any mission you want 3. **View Progress Dashboard** โ€” See your skill levels and rank 4. **Reflection Journal** (Terminal only) โ€” Review past journal entries and your practice chain 5. **VSCode Integration Guide** (Terminal only) โ€” Learn how to pair missions with VSCode 6. **Save Progress** โ€” Manually save your state 7. **Exit** โ€” Quit (progress is auto-saved) ### Mission Flow Each mission teaches a concept through: 1. **Philosophical Anchor** โ€” Connect code to a real-world principle 2. **Economic Parallel** โ€” Understand the "cost" and "value" of the pattern 3. **Technical Concept** โ€” Learn the actual code syntax 4. **Challenge** โ€” Answer a question or write code 5. **Reflection** (Terminal optional) โ€” Answer Uta Hagen's 9 systems-thinking questions ### Progression System - **Honor Points** โ€” Earned by completing missions (20-30 per mission) - **Ranks** โ€” Initiate โ†’ Apprentice โ†’ Practitioner โ†’ Adept โ†’ Expert โ†’ Co-Architect - **Skills** โ€” Track mastery: Python, Git, JSON, Architecture, Code Review (0-5 levels each) - **Practice Chain** โ€” Consecutive days of journaling (rewards deliberate practice, not perfection) - **Pathways** โ€” Beginner confidence โ†’ Contributor readiness โ†’ Mission authoring โ†’ Facilitator readiness --- ## ๐Ÿ’พ Progress Saves Your progress is stored in JSON files (shared between both frontends): ### `~/.dojo_save.json` (Player State) ```json { "name": "David", "honor": 150, "completed": ["git_system_grounding", "python_variables"], "skills": { "python": 2, "git": 3, "json": 1, "architecture": 0, "review": 0 }, "last_save": "2026-06-22T18:40:02Z" } ``` ### `~/.dojo_journal_data.json` (Reflection Entries โ€” Terminal only) ```json { "2026-06-22": { "timestamp": "2026-06-22T18:42:15Z", "mission_id": "python_variables", "mission_title": "Variables & Data Lineage", "player_rank": "Apprentice", "answers": { "Who am I in this circumstance?": "A programmer learning to think systematically...", "What do I want?": "To understand how data flows through systems..." } } } ``` --- ## ๐Ÿ”— VSCode Integration (Terminal Version) Pair each mission with VSCode for hands-on learning: ### One-Time Setup 1. Install [VSCode](https://code.visualstudio.com) 2. Install extensions: - **Python** (by Microsoft) โ€” Run and debug Python code - **GitLens** โ€” View Git history and blame - **Prettier** โ€” Auto-format JSON - **Python Indent** โ€” Smart indentation ### Workflow ```bash # In VSCode terminal: python dojo_classroom.py # In another VSCode editor: # 1. Complete a mission in the terminal # 2. Open ~/dojo_demo.json or other files created by missions # 3. Experiment and modify them # 4. Run code with F5 to see results ``` ### Official Tutorials to Pair With - **Missions 1-4** โ†’ [Python Quick Start](https://code.visualstudio.com/docs/python/python-quick-start) - **Missions 5-6** โ†’ [Source Control](https://code.visualstudio.com/docs/sourcecontrol/overview) - **Missions 7-8** โ†’ [Debugging](https://code.visualstudio.com/docs/python/debugging) - **Missions 9-10** โ†’ [Testing](https://code.visualstudio.com/docs/python/testing) --- ## ๐Ÿ“š Architecture (v5.0) ### Data-Driven Missions Missions are stored in `missions/missions.json` as pure data: ```json { "id": "git_system_grounding", "number": 1, "title": "System Grounding", "philosophy": "Like Tai Chi...", "economics": "Infrastructure is...", "lesson": "The terminal...", "challenge": "Type the command to clone a repository.", "answer": "git clone", "answertype": "contains", "skill": "git", "honorreward": 20 } ``` This means: - โœ… **Non-programmers can contribute missions** (educators, subject-matter experts) - โœ… **Translators can localize content** without touching Python - โœ… **Mission packs can be shared** and loaded dynamically - โœ… **Save files are future-proof** (mission IDs never change) - โœ… **Multiple frontends can coexist** (share the same mission/player data) ### Engine Architecture **Terminal Version:** - `dojo_classroom.py` โ€” Main game loop, mission loading, player management (v5.0 Dynamic Engine) - `missions/missions.json` โ€” All mission data (externalized, data-driven) - `~/.dojo_save.json` โ€” Player progress (persistent) - `~/.dojo_journal_data.json` โ€” Reflection entries (persistent, optional) **Browser Version:** - `dojo_web.html` โ€” Standalone single-page app (HTML/CSS/JavaScript) - Mission data embedded inline (no external dependencies) - Browser localStorage for persistent player progress ### Future Phases - **Phase 1 (Current)** โ€” 10 core Python/Git/JSON/CodeReview missions (Terminal + Browser) - **Phase 2** โ€” `dojo_ascension.py` โ€” Advanced multi-week curriculum (Linux, security, quant) - **Phase 3** โ€” Mission packs: Journalism, Genealogy, Governance, AI Literacy - **Phase 4** โ€” Community: Shared mission packs, classroom dashboards, instructor tools --- ## ๐Ÿงช Development & Testing ### One-Command Contributor Onboarding Use these exact commands: ```bash python newcomer.py python newcomer.py --play ``` - `python newcomer.py` installs dependencies, runs tests, and validates missions. - `python newcomer.py --play` does the same and then launches the game. ### Validate Missions ```bash python validate_missions.py ``` This checks for missing required fields, type mismatches, and schema issues. ### Run Tests ```bash python -m unittest discover -s tests ``` ### PR Merge Baseline (Required) Every PR must pass: - `python -m unittest discover -s tests` - `python validate_missions.py` For mission/content PRs, also complete the Definition of Done in [.github/pull_request_template.md](.github/pull_request_template.md). ### Generate Repo Bundle If you want a single text file for sharing with collaborators or AI tools: ```bash python refresh_repo_bundle.py ``` This regenerates [repo_bundle.txt](repo_bundle.txt) from the current repository contents. For fast AI orientation, also see [llms.txt](llms.txt) and [CONTEXT.md](CONTEXT.md). ### AI Collaborators Use these canonical, low-token entry points when working with agentic tools: - [llms.txt](llms.txt) - compact project manifest with key files and commands - [CONTEXT.md](CONTEXT.md) - onboarding context, structure, CI overview, and tone - [repo_bundle.txt](repo_bundle.txt) - full plain-text repository snapshot Public Pages URLs for URL-based tools: - https://solarpunkopensourcelaboratory.github.io/dojo-ascension/ - https://solarpunkopensourcelaboratory.github.io/dojo-ascension/llms.txt - https://solarpunkopensourcelaboratory.github.io/dojo-ascension/CONTEXT.md - https://solarpunkopensourcelaboratory.github.io/dojo-ascension/repo_bundle.txt ### Pilot Reporting Utilities For cohort reporting and grant evidence: ```bash python export_learner_data.py --data-dir ~/.dojo_ascension --format json python measure_retention.py --data-dir ~/.dojo_ascension ``` These commands aggregate anonymized learner outcomes from local save files. --- ## ๐Ÿค Contributing ### For Mission Writers See [CONTRIBUTING.md](CONTRIBUTING.md) for the full guide. Quick start: 1. Fork the repo 2. Use `missions/mission_template.json` to draft a mission 3. Review with `missions/MISSION_REVIEW_RUBRIC.md` 4. Test locally: `python newcomer.py` 5. Add your mission(s) to `missions/missions.json` 6. Submit a pull request Good first mission path for non-coders: - Start with one recall/application mission - Keep challenge and answer short - Add one optional hint - Ask for review using the mission rubric - Use plain language and inclusive examples (see [docs/ACCESSIBILITY_BASELINE.md](docs/ACCESSIBILITY_BASELINE.md)) ### Project Governance, Metrics, and Roadmap - Governance and response times: [docs/GOVERNANCE.md](docs/GOVERNANCE.md) - Public metrics: [docs/METRICS.md](docs/METRICS.md) - Pilot program: [docs/PILOT_PROGRAM.md](docs/PILOT_PROGRAM.md) - Funding kit: [docs/FUNDING_KIT.md](docs/FUNDING_KIT.md) - Impact measurement: [docs/IMPACT_MEASUREMENT.md](docs/IMPACT_MEASUREMENT.md) - Learner privacy: [docs/DATA_PRIVACY.md](docs/DATA_PRIVACY.md) - Accessibility testing: [docs/ACCESSIBILITY_TESTING.md](docs/ACCESSIBILITY_TESTING.md) - Roadmap: [ROADMAP.md](ROADMAP.md) - Monthly updates: [CHANGELOG.md](CHANGELOG.md) ### For Frontend Developers Want to build a new frontend (RPG, mobile, web framework, etc.)? 1. Load missions from `missions/missions.json` (or embed them) 2. Implement the same player state structure (see `~/.dojo_save.json` format) 3. Follow the answer validation logic (see `validate_answer()` in `dojo_classroom.py`) 4. Open an issue to discuss compatibility before building ### For Code Contributors - Bug fixes and feature requests welcome - See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup - **Zero-setup option:** click **Code โ†’ Codespaces โ†’ Create codespace** to get a ready-made Python environment in your browser (see [.devcontainer/devcontainer.json](.devcontainer/devcontainer.json)) ### Contributors We credit everyone who contributes code, missions, translations, accessibility, docs, facilitation, or review. See [CONTRIBUTORS.md](CONTRIBUTORS.md). --- ## ๐Ÿ“‹ Project Status **v5.0 (Current)** - โœ… Dynamic mission engine (missions.json) - โœ… Terminal frontend (Python) - โœ… Browser frontend (HTML/JavaScript) โ€” NEW! - โœ… Competency-based ranking - โœ… Uta Hagen reflection journal (Terminal only) - โœ… Dashboard with skill specialization - โœ… Persistent saves (JSON) - ๐Ÿ”œ Mission packs (Journalism, Genealogy, Governance) - ๐Ÿ”œ Classroom mode (instructor dashboard) - ๐Ÿ”œ Mobile app (iOS/Android) --- ## ๐Ÿ”ง Maintenance & Sustainability Dojo Ascension is intentionally small, legible, and inexpensive to maintain. - **Minimal dependencies** โ€” only small Python packages plus the standard library - **No required cloud backend** โ€” core use stays local-first - **Transparent governance** โ€” decisions, response times, and merge baselines are documented - **Grant-ready evidence** โ€” privacy, pilot, and impact docs are part of the repository - **Community maintenance model** โ€” contributors can help through code, curriculum, accessibility, documentation, facilitation, and translation Funding primarily supports maintenance, accessibility, curriculum expansion, pilot operations, and community stewardship. --- ## ๐Ÿ“– License GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. https://fsf.org/ Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. The GNU General Public License is a free, copileft license for software and other kinds of works. When we use your software, we must adhere to the spirit of cooperation and transparency that defines the open-source community. [Full GPL-3.0 text available at: https://www.gnu.org/licenses/gpl-3.0.txt] --- ## ๐Ÿ™ Credits Built by and for the SolarPunk community. Inspired by: - **Uta Hagen's acting techniques** (systems thinking) - **Cornel West's philosophy** (public justice) - **Bruce Lee's martial philosophy** (efficiency) - **Estonia's e-governance model** (resilience) - **Brazilian Jiu-Jitsu** (collaborative learning) --- ## ๐Ÿ”— Links - [GitHub Repository](https://github.com/solarpunkopensourcelaboratory/dojo-ascension) - [Contributing Guide](CONTRIBUTING.md) - [Mission & Funding One-Pager](FUNDING_ONE_PAGER.md) - [Governance](docs/GOVERNANCE.md) - [Public Metrics](docs/METRICS.md) - [SolarPunk Open Source Laboratory](https://github.com/solarpunkopensourcelaboratory) --- **Let us never stop learning from Galileo.** โ€” SolarPunk Opensource Laboratory ===== ROADMAP.md ===== # Roadmap ## Now - Run one publishable pilot cohort - Improve learner retention, momentum cues, and pathway clarity - Strengthen contributor pathways for educators, writers, translators, facilitators, and coders - Maintain monthly public metrics and updates ## Next - Mission packs for journalism, governance, genealogy, and AI literacy - Better cohort tooling and classroom-mode support - Accessibility and localization expansion ## Later - Instructor dashboards - broader multi-client ecosystem growth - mobile and other alternative frontends ## Funding priorities - Core maintenance and testing reliability - Curriculum expansion and review - Accessibility and localization work - Pilot facilitation and impact reporting - Community stewardship and contributor support ===== demo/dojo_demo_save.json ===== { "name": "DemoPlayer", "honor": 75, "completed": ["python_variables_lineage"], "skills": { "python": 2, "git": 1, "json": 1, "architecture": 0, "review": 0 }, "last_save": "2026-07-01T12:00:00Z" } ===== docs/ACCESSIBILITY_BASELINE.md ===== # Accessibility & Inclusion Baseline ## Content standards - Use plain language and define jargon on first use - Prefer short sentences and concrete examples - Use inclusive, non-gatekeeping prompts and analogies ## Web frontend checks (lightweight) For changes touching `dojo_web.html`, verify: 1. **Keyboard flow**: all interactive controls are reachable and usable with Tab/Shift+Tab/Enter/Space 2. **Contrast**: text is readable against backgrounds in default theme 3. **Readable layout**: content remains understandable on small screens and zoomed views ## Mission-writing checks - Challenge prompt is unambiguous - Hint helps without giving away answer immediately - Examples avoid assumptions about class, region, or prior privilege ===== docs/ACCESSIBILITY_TESTING.md ===== # Accessibility Testing Use this alongside `docs/ACCESSIBILITY_BASELINE.md` when changing learner-facing content or interfaces. ## Web frontend checks - Verify Tab / Shift+Tab / Enter / Space flow in `dojo_web.html` - Check contrast in the default theme - Check small-screen readability - Confirm mission prompts remain understandable at browser zoom ## Terminal checks - Keep prompts short and plain-language - Avoid relying on color alone for meaning - Keep menu choices predictable and easy to re-enter - Confirm important progress cues still make sense without journal use ## Content checks - define jargon on first use - use inclusive examples - keep hints helpful without giving away the answer immediately ## Pilot checks - ask what made learners feel included or excluded - note where extra facilitator intervention was needed - turn recurring confusion into documentation or mission updates ===== docs/DATA_PRIVACY.md ===== # Learner Data & Privacy Dojo Ascension is designed to support low-overhead educational use without requiring a central tracking service. ## What data is stored ### Terminal edition - learner name - honor, completed missions, and skill levels - session timestamps for retention reporting - optional reflection journal entries ### Browser edition - the same learner progress fields stored in browser local storage - no automatic server sync ## What is *not* required - no hosted database - no third-party analytics SDK - no mandatory account creation - no centralized learner surveillance for core use ## Storage locations - Terminal save: `~/.dojo_ascension/dojo_save.json` by default - Terminal journal: `~/.dojo_ascension/dojo_journal_data.json` by default - Browser save: local browser storage Environment overrides (`DOJO_DATA_DIR`, `DOJO_SAVE_FILE`, `DOJO_JOURNAL_FILE`) can isolate learners on shared machines. ## Classroom / nonprofit guidance - Prefer one save directory per learner on shared machines. - Explain clearly to learners what is stored and why. - Export only anonymized cohort aggregates when reporting pilot outcomes. - Treat reflection journals as optional and potentially sensitive. ## Export and deletion - Learners can delete local files directly. - Browser learners can clear local storage in the browser. - Facilitators can export anonymized progress summaries with: ```bash python export_learner_data.py --data-dir --format json ``` ## FERPA-compatible posture This repository is structured to make privacy-preserving classroom use feasible: - local-first data storage - no required cloud account - no default third-party telemetry - optional reflective writing instead of mandatory behavioral surveillance Organizations remain responsible for their own policies, consent practices, and compliance reviews. ===== docs/FUNDING_KIT.md ===== # Starter Funding Kit ## Narrative packet - Public promise (`FUNDING_ONE_PAGER.md`) - Governance and roadmap (`docs/GOVERNANCE.md`) - Metrics and monthly updates (`docs/METRICS.md`, `CHANGELOG.md`) - Pilot evidence (`docs/PILOT_PROGRAM.md`) - Privacy and impact measurement (`docs/DATA_PRIVACY.md`, `docs/IMPACT_MEASUREMENT.md`) ## Budget categories - Maintenance (bug fixes, dependency upkeep, CI reliability) - Curriculum expansion (new missions and mission packs) - Accessibility and inclusion (content review, usability improvements) - Community support (review operations, pilot facilitation, documentation) - Impact operations (cohort data export, pilot reporting, facilitator support) ## Evidence checklist before outreach - At least one monthly metrics update published - At least one pilot summary published - Clear roadmap with now/next/later priorities - Defined review and decision process documented - Privacy posture documented for classroom/nonprofit adoption - One example of contributor growth beyond code (mission writing, accessibility, translation, facilitation) - One concrete efficiency story for adopting organizations ===== docs/GOVERNANCE.md ===== # Governance and Delivery ## Reviewer roles - **Mission/content reviewers**: clarity, accessibility, educational intent - **Code reviewers**: behavior, safety, tests, maintainability - **Release steward**: monthly changelog, roadmap updates, and merge readiness ## Decision process 1. Open proposal (issue or PR) 2. Gather async feedback 3. Resolve concerns in public thread 4. Merge on maintainer approval and passing checks If maintainers disagree, choose the option with: 1) clearer learner value, 2) lower maintenance cost, 3) better interoperability. ## Response-time expectations - First maintainer response: within 7 days - Review follow-up after author update: within 7 days - Monthly status update: within first 7 days of each month ## Merge baseline - Unit tests pass - Mission validation passes - PR template checklist completed ## Roadmap ### Now - Reliable onboarding and contribution flow - Mission-writing support for non-coders - Accessibility baseline and review checklist ### Next - Pilot cohorts and feedback integration - Mission packs (journalism, governance, genealogy) - Better public metrics automation ### Later - Classroom dashboards - Localization and translation infrastructure - Multi-client ecosystem growth ===== docs/IMPACT_MEASUREMENT.md ===== # Impact Measurement This document turns the public metrics into a repeatable reporting workflow for pilots, grants, fellowships, and nonprofit adoption. ## Core questions - Are learners returning? - Are learners completing missions? - Are learners gaining confidence and belonging? - Are learners becoming contributors, authors, or facilitators? ## Quantitative metrics ### 1. 14-day learner return rate Definition: percent of learners with a second logged session within 14 days. ```bash python measure_retention.py --data-dir ``` ### 2. Median missions completed per active learner Definition: middle value of mission completions across learners in a cohort. ```bash python export_learner_data.py --data-dir --format json ``` ### 3. Contributor growth Definition: unique merged PR authors per month. Source: - GitHub PR activity - `CHANGELOG.md` ## Qualitative metrics - learner confidence after each session - whether learners felt included or excluded - facilitator notes about where support was required - examples of non-code contribution: mission writing, translation, accessibility review, documentation, facilitation ## Recommended cohort evidence bundle - anonymized export from local save files - retention rollup - 2-3 short learner quotations - 1 facilitator summary - 1 note on what changed in docs or missions after feedback ## Suggested reporting cadence - per session: learner/facilitator notes - per cohort: short pilot summary - monthly: `CHANGELOG.md` metrics update ## Minimal public summary template - cohort size - missions started and completed - 14-day return rate - median missions completed - confidence / belonging themes - contribution outcomes - next experiment ===== docs/METRICS.md ===== # Public Metrics Track these monthly in `CHANGELOG.md`. ## Core metrics - Active contributors (unique merged PR authors) - Merged mission/content PRs - Learner progression signals (mission completion and return rate) ## Decision metrics for funders and pilot partners - **14-day learner return rate** โ€” % of learners with a second session within 14 days - **Median missions completed per active learner** โ€” program momentum - **Confidence / belonging signal** โ€” short post-session self-report from learners - **Contribution behavior** โ€” issues opened, PRs opened, or missions authored by pilot participants - **Inclusion signal** โ€” whether learners felt welcomed, confused, included, or excluded ## Data capture (manual baseline) - GitHub PR activity for contributor and merge counts - Optional anonymized learner snapshots from pilot cohorts - Local save-file exports with `python export_learner_data.py --data-dir ` - Retention rollups with `python measure_retention.py --data-dir ` - Post-session facilitator notes using the pilot feedback template ## Reporting format Use one monthly entry with: - Metrics table (current month vs previous month) - What improved - What regressed - Next experiment ## Minimum monthly evidence bundle - `CHANGELOG.md` metrics entry - At least one pilot or cohort note when a cohort is active - One contributor/community signal (new mission author, new reviewer, translation help, accessibility feedback, etc.) ===== docs/PILOT_PROGRAM.md ===== # Tiny Pilot Program ## Goal Run one short pilot (2-4 weeks) with a small cohort to validate onboarding, mission clarity, and contributor flow. ## Cohort - 8-20 learners - 1-3 facilitators - Mix of coders and non-coders ## What to collect - Completion data: started vs completed missions - Retention signal: return within 14 days - Contributor signal: pilot participants opening issues/PRs - Qualitative feedback: confusion points, motivation, accessibility blockers - Confidence and belonging signal: short learner self-report after each session - Facilitator effort: how much support was needed per learner/session ## Feedback instrument (minimum) After each session, ask: 1. What was clear? 2. What was confusing? 3. What made you feel included or excluded? 4. What should we improve before inviting more learners? ## Publishable outcome target Aim for a simple before/after story that a grant reviewer can understand: - cohort size - learner completion count - 14-day return rate - confidence/belonging summary - contribution behavior (issues, PRs, or mission drafts) - 2-3 short learner/facilitator quotations Use: - `python export_learner_data.py --data-dir ` - `python measure_retention.py --data-dir ` - `.github/ISSUE_TEMPLATE/pilot_feedback.md` ## Closeout Publish a short pilot summary in `CHANGELOG.md` and update: - onboarding docs - mission author guidance - roadmap priorities ===== dojo_classroom.py ===== #!/usr/bin/env python3 """ DOJO ASCENSION v5.0 โ€” Dynamic Mission Engine + Reflection System A terminal RPG that teaches Python, Git, and JSON for SolarPunk contributor qualification. Missions are data-driven (missions.json). Learning is paired with Uta Hagen reflection. Author: alucardzagreus-boop / SolarPunk HackNet Pedagogical Model: Connectivism, Progressive Disclosure, Deliberate Practice """ import json import os import sys import time from datetime import datetime, timedelta from pathlib import Path try: from colorama import init, Fore, Style, Back init(autoreset=True) HAS_COLOR = True except ImportError: HAS_COLOR = False class _Dummy: def __getattr__(self, name): return "" Fore = Style = Back = _Dummy() # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ # CONFIGURATION & PATHS # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ def get_state_paths(): """Resolve save and journal paths, allowing per-user overrides on shared machines.""" data_dir = Path( os.environ.get( "DOJO_DATA_DIR", str( Path.home() / ".dojo_ascension"))).expanduser() data_dir.mkdir(parents=True, exist_ok=True) save_file = Path( os.environ.get( "DOJO_SAVE_FILE", str( data_dir / "dojo_save.json"))).expanduser() journal_file = Path( os.environ.get( "DOJO_JOURNAL_FILE", str( data_dir / "dojo_journal_data.json"))).expanduser() if not save_file.is_absolute(): save_file = data_dir / save_file if not journal_file.is_absolute(): journal_file = data_dir / journal_file save_file.parent.mkdir(parents=True, exist_ok=True) journal_file.parent.mkdir(parents=True, exist_ok=True) return save_file, journal_file SAVE_FILE, JOURNAL_FILE = get_state_paths() MISSIONS_FILE = Path(__file__).parent / "missions" / "missions.json" UTA_HAGEN_QUESTIONS = [ "Who am I in this circumstance?", "What are my circumstances?", "What do I want?", "Why do I want it?", "When is it?", "Where is it?", "What must I overcome?", "How will I accomplish my objective?", "What have I discovered?" ] SESSION_LOG_RETENTION_LIMIT = 60 PATHWAY_STAGE_THRESHOLDS = ( (3, "Beginner confidence"), (6, "Contributor readiness"), ) def current_timestamp_iso(): """Return an ISO timestamp for learner-state and impact metrics.""" return datetime.now().isoformat() # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ # UI & FORMATTING # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ def clear_screen(): os.system('cls' if os.name == 'nt' else 'clear') def print_slow(text, speed=0.015): for char in text: sys.stdout.write(char) sys.stdout.flush() time.sleep(speed) print() def divider(char="โ”€", width=60, color=Fore.CYAN): print(f"{color}{char * width}{Style.RESET_ALL}") def header(title, color=Fore.CYAN): divider("โ•", 60, color) print(f"{color}{Style.BRIGHT} {title}{Style.RESET_ALL}") divider("โ•", 60, color) def lesson_box(text): lines = text.strip().split("\n") print(f"\n{Back.BLUE}{Fore.WHITE}{' LESSON ':^60}{Style.RESET_ALL}") for line in lines: print(f" {Fore.CYAN}{line}{Style.RESET_ALL}") print() def challenge_box(text): lines = text.strip().split("\n") print(f"\n{Back.GREEN}{Fore.BLACK}{' CHALLENGE ':^60}{Style.RESET_ALL}") for line in lines: print(f" {Fore.GREEN}{line}{Style.RESET_ALL}") print() def hint_box(text): print(f"\n {Fore.YELLOW}๐Ÿ’ก HINT: {text}{Style.RESET_ALL}\n") def wait(): input(f"\n{Fore.WHITE}[ Press ENTER to continue... ]{Style.RESET_ALL}") # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ # MISSION LOADING # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ def load_missions(): """Load missions from the mission index or fallback to the mission files folder.""" mission_dir = MISSIONS_FILE.parent if MISSIONS_FILE.exists(): try: with open(MISSIONS_FILE, 'r', encoding='utf-8') as f: data = json.load(f) return data.get('missions', []) except (OSError, json.JSONDecodeError) as e: print(f"{Fore.RED}Error loading missions: {e}{Style.RESET_ALL}") sys.exit(1) mission_files = sorted(mission_dir.glob("mission*.json")) if not mission_files: print(f"{Fore.RED}Error: no mission files found in {mission_dir}{Style.RESET_ALL}") sys.exit(1) missions = [] for mission_path in mission_files: try: with open(mission_path, 'r', encoding='utf-8') as f: missions.append(json.load(f)) except (OSError, json.JSONDecodeError) as e: print(f"{Fore.RED}Error loading mission file {mission_path}: {e}{Style.RESET_ALL}") sys.exit(1) return missions # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ # PLAYER CLASS # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ class Player: RANK_THRESHOLDS = [ (0, "Initiate"), (50, "Apprentice"), (150, "Practitioner"), (300, "Adept"), (600, "Expert"), (1000, "Co-Architect") ] def __init__(self, name, honor=0, completed=None, skills=None): self.name = name self.honor = honor self.completed = set(completed or []) self.skills = skills or { "python": 0, "git": 0, "json": 0, "architecture": 0, "review": 0 } self.first_session_at = None self.last_session_at = None self.last_mission_at = None self.session_count = 0 self.session_log = [] self.load_state() self.start_session() def get_rank(self): """Competency-based rank""" for threshold, title in reversed(self.RANK_THRESHOLDS): if self.honor >= threshold: return title return "Initiate" def add_honor(self, points, skill=None): self.honor += points self.last_mission_at = current_timestamp_iso() print( f"\n{Fore.YELLOW}โšก +{points} HONOR POINTS | Total: {self.honor}{Style.RESET_ALL}") if skill and skill in self.skills: self.skills[skill] += 1 print( f"{Fore.CYAN}โ†‘ {skill.upper()} now Level " f"{self.skills[skill]}{Style.RESET_ALL}" ) self.save_state() def save_state(self, quiet=False): """Persist player progress to JSON""" state = { "name": self.name, "honor": self.honor, "completed": list(self.completed), "skills": self.skills, "first_session_at": self.first_session_at, "last_session_at": self.last_session_at, "last_mission_at": self.last_mission_at, "session_count": self.session_count, "session_log": self.session_log, "last_save": current_timestamp_iso() } try: SAVE_FILE.parent.mkdir(parents=True, exist_ok=True) with open(SAVE_FILE, 'w', encoding='utf-8') as f: json.dump(state, f, indent=2) if not quiet: print(f"{Fore.GREEN}โœ“ Progress saved to {SAVE_FILE}{Style.RESET_ALL}") except OSError as e: print(f"{Fore.RED}Error saving progress: {e}{Style.RESET_ALL}") def load_state(self): """Load player progress from JSON""" if SAVE_FILE.exists(): try: with open(SAVE_FILE, 'r', encoding='utf-8') as f: state = json.load(f) self.name = state.get("name", self.name) self.honor = state.get("honor", 0) self.completed = set(state.get("completed", [])) self.skills = state.get("skills", self.skills) self.first_session_at = state.get("first_session_at") self.last_session_at = state.get("last_session_at") self.last_mission_at = state.get("last_mission_at") self.session_count = state.get("session_count", 0) self.session_log = state.get("session_log", []) except (OSError, json.JSONDecodeError): pass # Default to new player if corrupted def start_session(self): """Record a learner session for retention and cohort reporting.""" current = current_timestamp_iso() if not self.first_session_at: self.first_session_at = current self.last_session_at = current self.session_count += 1 # Keep a bounded recent log so cohort metrics stay useful without # allowing save files to grow forever on long-running installs. self.session_log = ( self.session_log + [current] )[-SESSION_LOG_RETENTION_LIMIT:] self.save_state(quiet=True) # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ # UTA HAGEN JOURNAL SYSTEM # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ def load_journal_data(): """Load journal entries from JSON""" if JOURNAL_FILE.exists(): try: return json.loads(JOURNAL_FILE.read_text(encoding='utf-8')) except (OSError, json.JSONDecodeError): return {} return {} def save_journal_data(data): """Persist journal to JSON""" JOURNAL_FILE.parent.mkdir(parents=True, exist_ok=True) JOURNAL_FILE.write_text(json.dumps(data, indent=2), encoding='utf-8') def get_practice_chain(data): """Calculate consecutive practice days (replaces 'streak')""" chain = 0 current = datetime.now().date() while current.strftime("%Y-%m-%d") in data: chain += 1 current -= timedelta(days=1) return chain def journal_reflection(mission_id, mission_title, player): """Optional post-mission reflection using Uta Hagen questions""" choice = input( f"\n{Fore.CYAN}Would you like to journal about this mission? " f"(y/n): {Style.RESET_ALL}" ).strip().lower() if choice != 'y': return data = load_journal_data() today = datetime.now().strftime("%Y-%m-%d") if today in data: print(f"{Fore.YELLOW}You already journaled today.{Style.RESET_ALL}") return print(f"\n{Fore.BLUE}{'โ”€' * 60}") print(" UTA HAGEN SYSTEMS THINKING JOURNAL") print(f" Mission: {mission_title}") print(f"{'โ”€' * 60}{Style.RESET_ALL}\n") answers = {} for i, question in enumerate(UTA_HAGEN_QUESTIONS, 1): print(f"{Fore.WHITE}[{i}/9] {question}{Style.RESET_ALL}") answer = input(f"{Fore.GREEN}โ†’ {Style.RESET_ALL}").strip() answers[question] = answer data[today] = { "timestamp": datetime.now().isoformat(), "mission_id": mission_id, "mission_title": mission_title, "player_rank": player.get_rank(), "answers": answers } save_journal_data(data) print( f"\n{Fore.GREEN}โœ“ Reflection saved to {JOURNAL_FILE}" f"{Style.RESET_ALL}" ) print(f"{Fore.CYAN}Practice Chain: {get_practice_chain(data)} days{Style.RESET_ALL}") def view_journal(data): """Display past journal entries""" if not data: print( f"\n{Fore.YELLOW}No journal entries yet. Start by journaling " f"after a mission!{Style.RESET_ALL}" ) return clear_screen() header("REFLECTION JOURNAL โ€” PAST ENTRIES", Fore.BLUE) print( f"{Fore.CYAN}Practice Chain: {get_practice_chain(data)} " f"consecutive days{Style.RESET_ALL}\n" ) for date in sorted(data.keys(), reverse=True)[:10]: entry = data[date] print( f"{Fore.YELLOW}{date}{Style.RESET_ALL} โ€” " f"{entry.get('mission_title', 'Unknown')}" ) for question, answer in entry.get("answers", {}).items(): print(f" {Fore.WHITE}Q: {question}{Style.RESET_ALL}") print(f" {Fore.GREEN}A: {answer[:80]}...{Style.RESET_ALL}" if len( answer) > 80 else f" {Fore.GREEN}A: {answer}{Style.RESET_ALL}") print() wait() # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ # MISSION EXECUTION # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ def validate_answer(user_input, answer, answertype="exact"): """ Validate user answer against expected answer(s). answertype can be: - "exact": exact match (case-insensitive, whitespace-normalized) - "contains": substring match (case-insensitive) - default: treat as contains """ user = user_input.lower().strip() # Handle list of acceptable answers if isinstance(answer, list): for ans in answer: if validate_answer(user_input, ans, answertype): return True return False answer_str = str(answer).lower().strip() if answertype == "exact": return user == answer_str else: # "contains" or default return answer_str in user def run_code_challenge(prompt, answer, answertype="exact", hint=""): """Generic code challenge runner""" challenge_box(prompt) if hint: hint_box(hint) print( f" {Fore.WHITE}TIP: Try this in VSCode โ€” create a .py file and " f"run it there!{Style.RESET_ALL}" ) print( f"\n {Fore.YELLOW}โ†’ Type your answer, 'hint', or 'skip':" f"{Style.RESET_ALL}\n" ) attempts = 0 while True: try: user_input = input(f" {Fore.GREEN}>>> {Style.RESET_ALL}").strip() except (EOFError, KeyboardInterrupt): print("\nReturning to menu...") return False if user_input.lower() == 'skip': print(f" {Fore.YELLOW}Skipped. Revisit anytime.{Style.RESET_ALL}") return False if user_input.lower() == 'hint': hint_box(hint or "Think about what the challenge is asking.") continue attempts += 1 result = validate_answer(user_input, answer, answertype) if result is True: print(f"\n {Fore.GREEN}{Style.BRIGHT}โœ“ CORRECT!{Style.RESET_ALL}") if attempts == 1: print( f" {Fore.YELLOW}Bonus: First try! +5 honor{Style.RESET_ALL}") return True else: print( f"\n {Fore.RED}โœ— Not quite. Try again.{Style.RESET_ALL}" ) if attempts >= 3: print( f" {Fore.YELLOW}Hint (after 3 tries): {hint}" f"{Style.RESET_ALL}" ) def execute_mission(mission_data, player, missions): """Execute a mission from the data structure""" mid = mission_data["id"] num = mission_data["number"] title = mission_data["title"] philosophy = mission_data["philosophy"] economics = mission_data["economics"] lesson = mission_data.get("lesson", mission_data.get("techconcept", "")) challenge = mission_data["challenge"] answer = mission_data["answer"] answertype = mission_data.get("answertype", "contains") skill = mission_data["skill"] honor_base = mission_data.get("honorreward", mission_data.get("honor_base", 20)) hint = mission_data.get("hint", "") header(f"MISSION {num}: {title}", Fore.CYAN) print(f"{Fore.BLUE}๐Ÿ“œ PHILOSOPHICAL ANCHOR:{Style.RESET_ALL}") print(f" {philosophy}\n") print(f"{Fore.YELLOW}๐ŸŒ ECONOMIC PARALLEL:{Style.RESET_ALL}") print(f" {economics}\n") print(f"{Fore.CYAN}๐Ÿ’ป TECHNICAL CONCEPT:{Style.RESET_ALL}") print(f" {lesson}\n") wait() won = run_code_challenge( challenge, answer, answertype, hint) if won: first_completion = mid not in player.completed player.completed.add(mid) if first_completion: player.add_honor(honor_base, skill) print(f"\n{Fore.CYAN}โœ“ Mission {num} Complete!{Style.RESET_ALL}") else: player.last_mission_at = current_timestamp_iso() player.save_state(quiet=True) print( f"\n{Fore.CYAN}โœ“ Mission {num} reviewed again." f"{Style.RESET_ALL}" ) print( f"{Fore.YELLOW}Honor is only awarded on the first completion so " f"your progress stays trustworthy for pilots and cohorts." f"{Style.RESET_ALL}" ) next_mission = get_next_mission(missions=missions, completed_ids=player.completed) if next_mission: print( f"{Fore.MAGENTA}Next step: Mission {next_mission['number']} โ€” " f"{next_mission['title']}.{Style.RESET_ALL}" ) print( f"{Fore.BLUE}Pathway track: {get_pathway_stage(player, missions)}" f"{Style.RESET_ALL}" ) journal_reflection(mid, title, player) return True else: print( f"\n{Fore.YELLOW}Practice makes perfect. Return when you're ready." f"{Style.RESET_ALL}" ) return False def get_next_mission(missions, completed_ids): """Return the next unfinished mission in curriculum order.""" return next((m for m in missions if m["id"] not in completed_ids), None) def get_pathway_stage(player, missions): """Translate raw progress into a learner-facing pathway.""" completed = len(player.completed) total = len(missions) for threshold, stage in PATHWAY_STAGE_THRESHOLDS: if completed < min(threshold, total): return stage if completed < total: return "Mission authoring" return "Facilitator readiness" # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ # DASHBOARD # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ def show_dashboard(player, missions): """Display player progress and skill specialization""" clear_screen() header("DOJO ASCENSION โ€” PROGRESS DASHBOARD", Fore.CYAN) print( f"\n{Fore.WHITE}Player : {Style.BRIGHT}{player.name}" f"{Style.RESET_ALL}" ) print(f"{Fore.WHITE}Rank : {Fore.YELLOW}{player.get_rank()}{Style.RESET_ALL}") print(f"{Fore.WHITE}Honor : {Fore.YELLOW}{player.honor} pts{Style.RESET_ALL}") divider() print(f"\n{Fore.CYAN}SKILL SPECIALIZATION:{Style.RESET_ALL}") for skill, level in sorted(player.skills.items(), key=lambda x: -x[1]): bar = "โ–ˆ" * level + "โ–‘" * (5 - level) print(f" {skill.upper():<15} [{bar}] {level}/5") divider() print(f"\n{Fore.CYAN}MISSION PROGRESS:{Style.RESET_ALL}") print(f" Completed: {len(player.completed)}/{len(missions)}") completed_skills = set() for m in missions: if m["id"] in player.completed: completed_skills.add(m["skill"]) print( f" Skills Mastered: {', '.join(sorted(completed_skills)) or 'None yet'}\n" ) journal_data = load_journal_data() chain = get_practice_chain(journal_data) next_mission = get_next_mission(missions, player.completed) divider() print(f"\n{Fore.MAGENTA}MOMENTUM & PATHWAY:{Style.RESET_ALL}") print(f" Pathway Track : {get_pathway_stage(player, missions)}") print(f" Sessions Logged: {player.session_count}") print(f" Practice Chain: {chain} day(s)") if next_mission: print( f" Next Mission : {next_mission['number']}. " f"{next_mission['title']} ({next_mission['skill']})" ) else: print(" Next Mission : Completed core pathway โ€” mentor, document, or author a mission") if player.last_mission_at: print(f" Last Win : {player.last_mission_at}") print(" Cohort Prompt : Invite a peer or facilitator to join your next session") # Show next rank threshold next_thresholds = [ t for t, _ in Player.RANK_THRESHOLDS if t > player.honor] if next_thresholds: next_t = next_thresholds[0] bar_width = 30 filled = int((player.honor / next_t) * bar_width) print(f"{Fore.YELLOW}Next Rank Progress:{Style.RESET_ALL}") print( f" [{'โ–ˆ' * filled}{'โ–‘' * (bar_width - filled)}] {player.honor}/{next_t} honor") wait() # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ # CHRONICLE TIPS # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ def chronicle_tips(player, missions, journal_data): """Analyse session history and surface personalised practice tips.""" clear_screen() header("CHRONICLE TIPS โ€” PERSONALISED INSIGHTS", Fore.MAGENTA) tips = [] # โ”€โ”€ Journal / practice-chain analysis โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ chain = get_practice_chain(journal_data) total_entries = len(journal_data) if total_entries == 0: tips.append(( "๐Ÿ“” Start your reflection journal", "You haven't written any journal entries yet. After each mission " "choose 'y' to journal. Even one sentence per session compounds " "into powerful self-knowledge over time." )) elif chain == 0: tips.append(( "๐Ÿ”„ Rebuild your practice chain", f"You have {total_entries} past journal entr" f"{'y' if total_entries == 1 else 'ies'} but your current chain " "is broken. Return daily โ€” even a 5-minute session counts." )) elif chain < 3: tips.append(( f"๐ŸŒฑ Chain growing: {chain} day(s) โ€” keep going!", "Consistent micro-practice beats long irregular sessions. " "Aim to reach a 7-day chain to build a lasting habit." )) else: tips.append(( f"๐Ÿ”ฅ Practice chain: {chain} day(s) โ€” excellent consistency!", "You are showing up regularly. Now focus on depth: read your old " "journal entries and look for recurring blockers to work through." )) # โ”€โ”€ Mission-completion analysis โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ total_missions = len(missions) done = len(player.completed) remaining = [m for m in missions if m["id"] not in player.completed] if done == 0: tips.append(( "๐Ÿš€ Begin your first mission", "No missions completed yet โ€” select option 1 from the menu to " "start with Mission 1: System Grounding." )) elif done == total_missions: tips.append(( "๐Ÿ† All missions complete โ€” go deeper", "You've finished every available mission. Re-read your journal " "entries, contribute a new mission JSON to the community, or " "mentor another practitioner." )) else: next_m = remaining[0] tips.append(( f"โš”๏ธ Next frontier: Mission {next_m['number']} โ€” {next_m['title']}", f"You've completed {done}/{total_missions} missions. " f"Your next challenge focuses on the '{next_m['skill'].upper()}' " "skill โ€” tackle it in your next session." )) # โ”€โ”€ Skill-gap analysis โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ skill_levels = player.skills if skill_levels: weakest_skill = min(skill_levels, key=lambda s: skill_levels[s]) weakest_level = skill_levels[weakest_skill] strongest_skill = max(skill_levels, key=lambda s: skill_levels[s]) strongest_level = skill_levels[strongest_skill] if weakest_level == 0: tips.append(( f"๐Ÿ“‰ Untouched skill: {weakest_skill.upper()}", f"Your {weakest_skill.upper()} skill is at 0. Look for " f"missions tagged '{weakest_skill}' to start building it." )) elif strongest_level - weakest_level >= 2: tips.append(( f"โš–๏ธ Balance your skills: lift {weakest_skill.upper()} " f"(lvl {weakest_level}) toward {strongest_skill.upper()} " f"(lvl {strongest_level})", "A well-rounded practitioner avoids over-specialisation. " f"Seek out {weakest_skill.upper()}-tagged missions to close " "the gap." )) else: tips.append(( f"โœ… Skills are balanced (strongest: {strongest_skill.upper()} " f"lvl {strongest_level})", "Keep progressing evenly. Each new mission advances a " "specific skill โ€” check the mission list to plan ahead." )) # โ”€โ”€ Reflection-quality nudge (based on journal content length) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ if journal_data: short_entries = sum( 1 for entry in journal_data.values() if all(len(a) < 20 for a in entry.get("answers", {}).values()) ) if short_entries > 0: tips.append(( "โœ๏ธ Go deeper in your reflections", f"{short_entries} of your journal entr" f"{'y has' if short_entries == 1 else 'ies have'} very short " "answers. The Uta Hagen questions reward specificity โ€” aim " "for at least one full sentence per question." )) # โ”€โ”€ Honor-rank nudge โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ next_thresholds = [ (t, title) for t, title in Player.RANK_THRESHOLDS if t > player.honor ] if next_thresholds: next_t, next_title = next_thresholds[0] gap = next_t - player.honor tips.append(( f"๐ŸŽ–๏ธ {gap} honor until rank: {next_title}", f"You are at {player.honor} honor. Keep completing missions to " "reach the next rank and unlock new challenges." )) # โ”€โ”€ Render โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ print( f"\n{Fore.CYAN}Session snapshot for {Style.BRIGHT}{player.name}" f"{Style.RESET_ALL}{Fore.CYAN} | " f"{done}/{total_missions} missions | " f"{total_entries} journal entr" f"{'y' if total_entries == 1 else 'ies'} | " f"chain {chain} day(s){Style.RESET_ALL}\n" ) divider() for i, (headline, detail) in enumerate(tips, 1): print(f"\n{Fore.YELLOW}{Style.BRIGHT}[{i}] {headline}{Style.RESET_ALL}") print(f" {Fore.WHITE}{detail}{Style.RESET_ALL}") divider() wait() # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ # VSCODE GUIDE # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ def show_vscode_guide(): """Display VSCode integration instructions""" clear_screen() header("VSCODE INTEGRATION GUIDE", Fore.GREEN) print(""" SETUP (one-time): โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ 1. Install VSCode: https://code.visualstudio.com 2. Extensions (Ctrl+Shift+X): โ€ข Python by Microsoft โ€ข GitLens (Git superpowers) โ€ข Prettier (JSON formatting) โ€ข Python Indent (auto-indentation) OFFICIAL TUTORIALS (pair with missions): โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Missions 1-4 โ†’ code.visualstudio.com/docs/python/python-quick-start Missions 5-6 โ†’ code.visualstudio.com/docs/sourcecontrol/overview Missions 7-8 โ†’ code.visualstudio.com/docs/python/debugging Missions 9-10 โ†’ code.visualstudio.com/docs/python/testing DAILY WORKFLOW: โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ 1. Open dojo-ascension folder in VSCode 2. Open terminal (Ctrl+`) โ†’ python dojo_classroom.py 3. Complete a mission in the terminal 4. Open ~/dojo_*.json files in VSCode 5. Experiment and modify them KEY SHORTCUTS: โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ F5 โ†’ Run Python file F9 โ†’ Toggle breakpoint (debugger) Ctrl+` โ†’ Open integrated terminal Ctrl+Shift+P โ†’ Command palette Ctrl+Shift+G โ†’ Git panel (view changes, commits) Ctrl+Shift+X โ†’ Extensions marketplace """) wait() # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ # MAIN MENU # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ def main_menu(player, missions): """Main menu interface""" clear_screen() header(f"DOJO OS v5.0 | {player.name} | {player.get_rank()}", Fore.CYAN) print( f"\n {Fore.YELLOW}โšก Honor: {player.honor} | Completed: " f"{len(player.completed)}/{len(missions)}{Style.RESET_ALL}\n" ) next_mission = get_next_mission(missions, player.completed) print( f" {Fore.MAGENTA}Pathway: {get_pathway_stage(player, missions)}" f"{Style.RESET_ALL}" ) if next_mission: print( f" {Fore.CYAN}Next recommended mission: {next_mission['number']}. " f"{next_mission['title']}{Style.RESET_ALL}\n" ) print(f" {Fore.WHITE}1. Start Next Mission{Style.RESET_ALL}") print(f" {Fore.WHITE}2. Choose Specific Mission{Style.RESET_ALL}") print(f" {Fore.WHITE}3. View Progress Dashboard{Style.RESET_ALL}") print(f" {Fore.WHITE}4. Reflection Journal{Style.RESET_ALL}") print(f" {Fore.WHITE}5. VSCode Integration Guide{Style.RESET_ALL}") print(f" {Fore.WHITE}6. Save Progress{Style.RESET_ALL}") print(f" {Fore.WHITE}7. Exit{Style.RESET_ALL}") print( f"\n {Fore.MAGENTA}/chronicle tips{Fore.WHITE} โ€” personalised tips " f"from your session history{Style.RESET_ALL}" ) return input(f"\n {Fore.GREEN}root@dojo:~# {Style.RESET_ALL}").strip() def game_loop(): """Main game loop""" global SAVE_FILE, JOURNAL_FILE SAVE_FILE, JOURNAL_FILE = get_state_paths() missions = load_missions() clear_screen() print_slow( f"{Fore.CYAN}{Style.BRIGHT} DOJO ASCENSION v5.0{Style.RESET_ALL}", 0.03) print_slow( f"{Fore.WHITE} Dynamic Mission Engine + Reflection System{Style.RESET_ALL}", 0.02) print_slow( f"{Fore.YELLOW} Python | Git | JSON | Code Review{Style.RESET_ALL}", 0.02) print() if SAVE_FILE.exists(): with open(SAVE_FILE, 'r', encoding='utf-8') as f: saved = json.load(f) player = Player( saved["name"], saved["honor"], saved.get( "completed", []), saved.get("skills")) print( f"{Fore.CYAN}โœ“ Save found! Welcome back, {player.get_rank()} " f"{player.name}.{Style.RESET_ALL}" ) else: name = input( f"\n {Fore.GREEN}Enter your name, Initiate: {Style.RESET_ALL}" ).strip() or "Initiate" player = Player(name) wait() while True: choice = main_menu(player, missions) if choice == '1': # Find next unfinished mission next_mission = None for m in missions: if m["id"] not in player.completed: next_mission = m break if next_mission: execute_mission(next_mission, player, missions) else: print( f"\n{Fore.GREEN}โœ“ All missions complete! You are a Co-Architect!{Style.RESET_ALL}") time.sleep(2) elif choice == '2': clear_screen() print(f"\n{Fore.CYAN}Available Missions:{Style.RESET_ALL}\n") for m in missions: status = "โœ“" if m["id"] in player.completed else " " print(f" [{status}] {m['number']}. {m['title']}") try: num = int( input( f"\n{Fore.GREEN}Choose mission number: " f"{Style.RESET_ALL}" ) ) mission = next( (m for m in missions if m["number"] == num), None) if mission: execute_mission(mission, player, missions) else: print(f"{Fore.RED}Mission not found.{Style.RESET_ALL}") time.sleep(2) except ValueError: pass elif choice == '3': show_dashboard(player, missions) elif choice == '4': journal_data = load_journal_data() view_journal(journal_data) elif choice == '5': show_vscode_guide() elif choice == '6': player.save_state() time.sleep(1) elif choice == '7': player.save_state() print_slow( f"\n{Fore.CYAN}Disconnecting from Dojo OS... Progress saved." f"{Style.RESET_ALL}" ) sys.exit(0) elif choice.lower() in ('/chronicle tips', '/chronicle'): journal_data = load_journal_data() chronicle_tips(player, missions, journal_data) if __name__ == "__main__": try: game_loop() except KeyboardInterrupt: print( f"\n{Fore.BLUE}The Dojo remains. Return when ready." f"{Style.RESET_ALL}" ) sys.exit(0) ===== dojo_web.html ===== ๐Ÿฅ‹ DOJO ASCENSION v5.0 โ€” Browser Edition

๐Ÿฅ‹ DOJO ASCENSION v5.0

Low-overhead digital literacy infrastructure for cohorts, classrooms, libraries, and community labs teaching Python, Git, JSON, and Code Review

Welcome, Initiate

You stand at the threshold of the Dojo.

Here, code is not mere syntaxโ€”it is a framework for thinking about systems, stewardship, and community infrastructure.

By mastering these crafts, you will learn to:

๐Ÿ“š Build resilient digital infrastructure (Git, version control, deployment)

๐Ÿค Design cooperative systems (OOP, APIs, data interoperability)

โœ… Hold systems accountable (Code review, testing, documentation)

๐ŸŒฑ Join a mission-aligned learning pathway from beginner confidence to contributor readiness

What is your name, seeker?

Mission Title

๐Ÿ“œ PHILOSOPHICAL ANCHOR

๐ŸŒ ECONOMIC PARALLEL

๐Ÿ’ป TECHNICAL CONCEPT

โš”๏ธ CHALLENGE

๐Ÿ’ก HINT:

Choose a Mission

PROGRESS DASHBOARD

Player Info

Name:

Rank:

Honor: 0 pts

Next Rank Progress

Skill Specialization

Mission Progress

Completed: 0 / 10

Skills Mastered: None yet

Momentum & Pathway

Beginner confidence

Sessions logged: 0

Next mission: Mission 1. System Grounding

Last mission win: No completions yet

Cohort cue: Invite a facilitator or peer to join your next session.

About the Dojo

๐ŸŽฏ What Is This?

Dojo Ascension is a lightweight learning operating system that combines:

  • Mission-based progression โ€” Complete 10 interconnected challenges
  • Competency tracking โ€” Master specific skills (Python, Git, JSON, Architecture, Code Review)
  • Persistent saves โ€” All progress stored in your browser
  • Cross-disciplinary context โ€” Each mission connects code to philosophy, economics, and martial arts
  • Institutional efficiency โ€” Reusable curriculum for classrooms, libraries, cohorts, and nonprofits without a heavyweight LMS

๐Ÿ“Š Progression System

  • Honor Points โ€” Earned by completing missions (20-30 per mission)
  • Ranks โ€” Initiate โ†’ Apprentice โ†’ Practitioner โ†’ Adept โ†’ Expert โ†’ Co-Architect
  • Skills โ€” Track mastery: Python, Git, JSON, Architecture, Code Review (0-5 levels each)

๐Ÿ“š 10 Core Missions

# Mission Skill Philosophy
1 System Grounding Git Wu Wei
2 Variables & Data Lineage Python Genealogy
3 JSON โ€” Data Language JSON Cooperation
4 Functions & Jeet Kune Do Python Efficiency
5 Git โ€” Journalistic Integrity Git Accountability
6 Git โ€” Branching & Merging Git Wing Chun
7 APIs & Digital Journalism Architecture Information Freedom
8 File I/O โ€” Institutional Memory Python Bushido
9 OOP โ€” Sociological Modeling Architecture Theatre
10 Code Review โ€” Gentle Art Review BJJ
===== export_learner_data.py ===== #!/usr/bin/env python3 """Aggregate anonymized learner metrics from Dojo save files.""" from __future__ import annotations import argparse import csv import hashlib import json from datetime import datetime from pathlib import Path PATHWAY_STAGE_THRESHOLDS = ( (3, "Beginner confidence"), (6, "Contributor readiness"), (10, "Mission authoring"), ) def parse_iso(value: str | None) -> datetime | None: if not value: return None try: return datetime.fromisoformat(value) except ValueError: return None def iter_candidate_files(data_dir: Path): for path in sorted(data_dir.rglob("*.json")): if path.name == "dojo_journal_data.json": continue yield path def load_learner_records(data_dir: Path) -> list[dict]: records = [] for path in iter_candidate_files(data_dir): try: payload = json.loads(path.read_text(encoding="utf-8")) except (OSError, json.JSONDecodeError): continue if not isinstance(payload, dict): continue if "completed" not in payload or "skills" not in payload: continue completed = payload.get("completed", []) skills = payload.get("skills", {}) stable_identity = f"{payload.get('name', '')}|{payload.get('first_session_at', '')}" learner_id = hashlib.sha256(stable_identity.encode("utf-8")).hexdigest()[:16] session_log = payload.get("session_log", []) records.append( { "learner_id": learner_id, "missions_completed": len(completed), "honor": payload.get("honor", 0), "skills_mastered": len([k for k, v in skills.items() if v > 0]), "pathway_stage": get_pathway_stage(len(completed)), "first_session_at": payload.get("first_session_at"), "last_session_at": payload.get("last_session_at"), "last_mission_at": payload.get("last_mission_at"), "session_count": payload.get("session_count", len(session_log)), "session_log_count": len(session_log), "session_log": session_log, "returned_within_14_days": returned_within_days(session_log, 14), } ) return records def get_pathway_stage(completed: int) -> str: for threshold, stage in PATHWAY_STAGE_THRESHOLDS: if completed < threshold: return stage return "Facilitator readiness" def returned_within_days(session_log: list[str], days: int) -> bool: """Return whether a learner had an early return within N days of session 1.""" parsed = [parse_iso(item) for item in session_log] parsed = [item for item in parsed if item is not None] if len(parsed) < 2: return False start = parsed[0] for item in parsed[1:]: if (item - start).days <= days: return True return False def aggregate(records: list[dict]) -> dict: learner_count = len(records) if learner_count == 0: return { "learner_count": 0, "median_missions_completed": 0, "retained_within_14_days": 0, "average_sessions": 0, } mission_counts = sorted(record["missions_completed"] for record in records) mid = learner_count // 2 if learner_count % 2: median = mission_counts[mid] else: median = (mission_counts[mid - 1] + mission_counts[mid]) / 2 retained = sum(1 for record in records if record["returned_within_14_days"]) average_sessions = round( sum(record["session_count"] for record in records) / learner_count, 2 ) return { "learner_count": learner_count, "median_missions_completed": median, "retained_within_14_days": retained, "retention_rate_percent": round((retained / learner_count) * 100, 2), "average_sessions": average_sessions, } def export_json(output: Path, summary: dict, records: list[dict]) -> None: output.write_text( json.dumps({"summary": summary, "learners": records}, indent=2), encoding="utf-8", ) def export_csv(output: Path, records: list[dict]) -> None: fieldnames = [ "learner_id", "missions_completed", "honor", "skills_mastered", "pathway_stage", "first_session_at", "last_session_at", "last_mission_at", "session_count", "session_log_count", "returned_within_14_days", ] with output.open("w", encoding="utf-8", newline="") as handle: writer = csv.DictWriter(handle, fieldnames=fieldnames, extrasaction="ignore") writer.writeheader() writer.writerows(records) def main() -> int: parser = argparse.ArgumentParser( description="Export anonymized learner metrics from Dojo save files." ) parser.add_argument( "--data-dir", type=Path, required=True, help="Directory containing learner save files.", ) parser.add_argument( "--format", choices=("json", "csv"), default="json", help="Output format for learner metrics.", ) parser.add_argument( "--output", type=Path, help="Optional output file. Defaults to learner_metrics.{json,csv}.", ) args = parser.parse_args() records = load_learner_records(args.data_dir) summary = aggregate(records) output = args.output or Path(f"learner_metrics.{args.format}") if args.format == "json": export_json(output, summary, records) else: export_csv(output, records) print(json.dumps(summary, indent=2)) print(f"Saved {len(records)} learner records to {output}") return 0 if __name__ == "__main__": raise SystemExit(main()) ===== llms.txt ===== Dojo Ascension - AI Collaborator Manifest Purpose - Community-first learning system for Python, Git, JSON, and systems thinking. - Data-driven missions with contributor pathways for code, docs, and education. Canonical URLs - https://solarpunkopensourcelaboratory.github.io/dojo-ascension/ - https://solarpunkopensourcelaboratory.github.io/dojo-ascension/llms.txt - https://solarpunkopensourcelaboratory.github.io/dojo-ascension/CONTEXT.md - https://solarpunkopensourcelaboratory.github.io/dojo-ascension/repo_bundle.txt Core Files - dojo_classroom.py (terminal runtime) - dojo_web.html (browser runtime) - missions/missions.json (canonical mission data) - CONTRIBUTING.md and .github/pull_request_template.md (contributor workflow) Quick Commands - python newcomer.py - python newcomer.py --play - python -m unittest discover -s tests - python validate_missions.py - python refresh_repo_bundle.py AI Loading Order 1. CONTEXT.md 2. llms.txt 3. repo_bundle.txt 4. task-specific files Project Tone - Clarity over jargon - Systems thinking over syntax memorization - Community stewardship and constructive review Follow-Up (Deferred) - Segmented bundles: bundle_missions.txt, bundle_docs.txt, bundle_code.txt ===== measure_retention.py ===== #!/usr/bin/env python3 """Measure learner return rate from Dojo save files.""" from __future__ import annotations import argparse import json from pathlib import Path from export_learner_data import load_learner_records, returned_within_days def main() -> int: parser = argparse.ArgumentParser( description="Measure 14-day learner return rate from Dojo save files." ) parser.add_argument( "--data-dir", type=Path, required=True, help="Directory containing learner save files.", ) parser.add_argument( "--days", type=int, default=14, help="Return window in days. Defaults to 14.", ) args = parser.parse_args() records = load_learner_records(args.data_dir) eligible = len(records) retained = sum( 1 for record in records if returned_within_days(record.get("session_log", []), args.days) ) rate = round((retained / eligible) * 100, 2) if eligible else 0.0 print( json.dumps( { "eligible_learners": eligible, "retained_within_window": retained, "window_days": args.days, "retention_rate_percent": rate, }, indent=2, ) ) return 0 if __name__ == "__main__": raise SystemExit(main()) ===== missions/MISSION_REVIEW_RUBRIC.md ===== # Mission Review Rubric (Minimal) Use this for mission/content PRs. ## 1) Clarity - [ ] Mission prompt is unambiguous - [ ] Technical explanation avoids unnecessary jargon - [ ] Expected answer is testable with current validation behavior ## 2) Accessibility & Inclusion - [ ] Language is plain and learner-friendly - [ ] Examples are culturally inclusive and non-gatekeeping - [ ] Hint helps learning without immediately revealing the answer ## 3) Educational Value - [ ] Philosophy anchor supports understanding (not just decoration) - [ ] Economics/system framing is relevant and coherent - [ ] Challenge difficulty matches honor reward ## 4) Quality Baseline - [ ] `python -m unittest discover -s tests` passes - [ ] `python validate_missions.py` passes ===== missions/mission01.json ===== { "id": "git_system_grounding", "number": 1, "title": "System Grounding", "skill": "git", "tag": "Git", "philosophy": "Like Tai Chi, where you must feel the ground before moving, a programmer must understand their environment. 'Wu Wei' is achieved when your tools become extensions of your mind.", "economics": "Infrastructure is the basis of all macro-economic stability. Before building goods, we build roads. Here, your terminal and shell are your economic infrastructure.", "techconcept": "The terminal is your direct interface with the system. Git is your tool for interacting with the open-source global supply chain of ideas.", "lesson": "Before you write any code, you need to know where you are and how to move. The terminal lets you navigate, inspect, and control your environment. Git uses that same terminal to connect your local work to remote repositories and collaborators.", "challenge": "Type the command to clone a git repository.", "hint": "The pattern is: git clone ", "answer": "git clone", "answertype": "contains", "honorreward": 20, "bonusfirsttry": 5 } ===== missions/mission02.json ===== { "id": "python_variables_lineage", "number": 2, "title": "Variables & Data Lineage", "skill": "python", "tag": "Python Core", "philosophy": "Genealogy teaches us that tracing lineage reveals identity. Analogy: A variable is not merely a box; it is a named node in a data lineage. Just as ancestral lineages carry traits forward in families, variables preserve and carry state across time in an execution scope.", "economics": "Micro-economics teaches that resources are scarce and optimization is mandatory. Memory is our system's scarcest resource. Analogy: Naming variables clearly acts as a public ledger of memory allocation, preventing 'naming inflation' (where poorly named variables clog developer cognitive capacity).", "techconcept": "In Python, variables are dynamically typed. When you assign `seed = 42`, Python allocates the value `42` in memory and points the name `seed` to it. The `=` operator does not mean equality (like in math); it represents assignment, setting the flow of data.", "lesson": "In this lesson, you will practice basic variable assignment. Remember, Python is case-sensitive, and variable names should ideally use snake_case to maintain community readability standards.", "challenge": "Write exactly this: assign the integer 42 to a variable named 'seed'.", "hint": "Create a variable named 'seed' on the left, an equals sign in the middle, and the integer 42 on the right.", "answer": ["seed = 42", "seed=42"], "answertype": "exact", "honorreward": 20, "bonusfirsttry": 5 } ===== missions/mission03.json ===== { "id": "json_data_language", "number": 3, "title": "JSON โ€” The Data Language", "skill": "json", "tag": "JSON Data", "philosophy": "Interoperability is what cooperation looks like in code. When diverse, self-determined systems must collaborate, they need a non-coercive, neutral language to exchange state. JSON represents this shared public language.", "economics": "Analogy: Global trade requires a frictionless transactional medium. JSON acts as a universal, lightweight currency of data exchange, allowing a Python server in Estonia to instantly trade structured information with a Javascript browser in Mexico without currency exchange fees (data translation overhead).", "techconcept": "JavaScript Object Notation (JSON) is a text format built on two structures: key-value pairs (`{}`) and ordered lists (`[]`). Every key must be a double-quoted string. Unlike Python, JSON uses lowercase `true`, `false`, and `null` instead of `True`, `False`, and `None`.", "lesson": "Understanding how JSON maps to native programming data structures is critical for web services, API communications, and saving user state securely.", "challenge": "What does JSON stand for? (Type out the 3 words cleanly, no extra punctuation)", "hint": "The first word is JavaScript, followed by the word for an entity (Object), and ending with representation (Notation).", "answer": "JavaScript Object Notation", "answertype": "exact", "honorreward": 20, "bonusfirsttry": 5 } ===== missions/mission04.json ===== { "id": "python_functions_logic", "number": 4, "title": "Functions & 'Jeet Kune Do' Logic", "skill": "python", "tag": "Python Core", "philosophy": "Bruce Lee's martial art, Jeet Kune Do, champions 'the style of no style'โ€”using only what works and discarding the rest with maximum efficiency. Analogy: A Python function encapsulates this logic. It is a precise, clean strike of logic that does exactly what is required with no wasted energy or syntactic clutter.", "economics": "Analogy: A function is a specialized cooperative workspace. Rather than every citizen building their own solar panels from scratch (repetitive code), the community delegates the task to a specialized workshop (a function) that takes raw materials (inputs) and yields finished utility (returns).", "techconcept": "Python functions are defined using the `def` keyword, followed by the function name, parentheses containing parameters, and a colon. Code blocks must be indented (PEP 8 recommends 4 spaces). The `return` keyword is vital; it passes computed results back to the caller.", "lesson": "In this lesson, we isolate execution logic. Functions allow us to practice the DRY principle (Don't Repeat Yourself), which prevents intellectual duplication in software ecosystems.", "challenge": "What Python keyword is used to define a function?", "hint": "It is a three-letter abbreviation of the word 'define'.", "answer": "def", "answertype": "contains", "honorreward": 20, "bonusfirsttry": 5 } ===== missions/mission05.json ===== { "id": "git_journalistic_integrity", "number": 5, "title": "Git โ€” Journalistic Integrity", "skill": "git", "tag": "Git Version Control", "philosophy": "Social justice relies on historical verifiability. Without transparent tracking of changes, systems of power can silently alter agreements. Analogy: Git is our cryptographic, immutable ledger of journalistic truth, preserving the timeline of who altered the system, when, and why.", "economics": "Estonia's e-governance model achieves high institutional trust because citizens can trace exactly who accesses or changes their digital records. Analogy: Git operates on a similar mathematical foundation (Merkle trees), creating a trust infrastructure where bad faith changes are structurally impossible to hide.", "techconcept": "Git does not track file differences; it tracks snapshots of your files. When you commit, Git records a cryptographic SHA-1 hash of your current staging area. The three stages of Git are: Working Directory (modifying files) -> Staging Area (`git add`) -> Git Repository (`git commit`).", "lesson": "We will practice the foundational Git workflow. Staging your files before committing is like proofreading your article before sending it to the press.", "challenge": "What exact command stages all current modifications in your directory for a Git commit?", "hint": "It starts with 'git add' and ends with a symbol representing the current directory.", "answer": "git add .", "answertype": "exact", "honorreward": 20, "bonusfirsttry": 5 } ===== missions/mission06.json ===== { "id": "git_branching_merging", "number": 6, "title": "Git โ€” Branching & Merging", "skill": "git", "tag": "Git Version Control", "philosophy": "In Wing Chun, the exercise of Chi Sao (sticky hands) teaches practitioners to remain in sensitive physical contact with their opponent, feeling changes in pressure and deflecting force dynamically. Analogy: Git branching and merging is our technical Chi Sao. It allows multiple developers to work in parallel flows of time, sensitively merging their inputs while resolving systemic merge conflicts smoothly.", "economics": "In economic development, monolithic markets crash easily. Resilience requires pluralism. Analogy: Branching is our R&D sandbox. By isolating a feature branch, we allow safe experimentation without risking the stability of our primary economy (the `main` branch).", "techconcept": "A branch in Git is simply a lightweight, movable pointer to a specific commit. Creating a branch is fast and consumes virtually zero disk space because Git only records the new pointer, not a full duplicate copy of your files.", "lesson": "In this lesson, you will practice creating isolated lines of development. Modern Git workflows use `git switch` or `git checkout` to move between these lines of code safely.", "challenge": "What command would you type to create and switch to a new branch named 'feature'?", "hint": "You can use 'git checkout' with a specific flag, or the modern 'git switch' with its creation flag.", "answer": ["git checkout -b feature", "git switch -c feature"], "answertype": "exact", "honorreward": 25, "bonusfirsttry": 5 } ===== missions/mission07.json ===== { "id": "architecture_apis_http", "number": 7, "title": "APIs & HTTP โ€” The Digital Journalist", "skill": "architecture", "tag": "Python+JSON", "philosophy": "Investigative journalism relies on primary sources. To hold systems accountable, we cannot wait to be spoon-fed data; we must query the source directly. Analogy: APIs act as the Freedom of Information Act (FOIA) of the digital world.", "economics": "Information asymmetry causes market failure. Open APIs democratize data, allowing independent actors to build tools that correct corporate or governmental monopolies on knowledge.", "techconcept": "An API (Application Programming Interface) allows two programs to talk. In Python, we use the `requests` library to GET data from URLs over HTTP. A 200 status code means success; a 404 means not found.", "lesson": "You will practice querying a system. Knowing how to request data programmatically is the foundational skill for building web scrapers, automation bots, and data journalism pipelines.", "challenge": "What standard Python library is used to make HTTP requests to web APIs?", "hint": "It is a plural word meaning 'to ask for things'.", "answer": ["requests", "import requests"], "answertype": "contains", "honorreward": 25, "bonusfirsttry": 5 } ===== missions/mission08.json ===== { "id": "python_file_io", "number": 8, "title": "File I/O โ€” Institutional Memory", "skill": "python", "tag": "Python+JSON", "philosophy": "The Bushido code values legacy. Without memory, a society cannot learn or grow. Analogy: Writing data to a file ensures that your digital actions echo into the future, much like Estonia's resilient national data registry.", "economics": "Capital accumulation requires secure storage. If a program loses all its data when it closes, it cannot generate generational wealth. Analogy: File I/O serves as the banking system of our program.", "techconcept": "Python uses `open('file.txt', 'w')` to write to a file, and `'r'` to read. Using the `with` keyword creates a 'context manager'โ€”it ensures the file is safely closed when we are done, preventing memory leaks and data corruption.", "lesson": "We will test your understanding of context managers. Safe File I/O is exactly how our Dojo OS saves your honor points, practice chain, and journal entries behind the scenes!", "challenge": "What Python keyword is used to open a file and ensure it closes automatically?", "hint": "It is a four-letter word that pairs with 'open'.", "answer": ["with"], "answertype": "exact", "honorreward": 20, "bonusfirsttry": 5 } ===== missions/mission09.json ===== { "id": "architecture_oop_modeling", "number": 9, "title": "OOP โ€” Sociological Modeling", "skill": "architecture", "tag": "Python OOP", "philosophy": "Theatre teaches us that a play requires Roles (scripts/blueprints) and Actors (individuals performing the role). Analogy: Object-Oriented Programming (OOP) applies sociology to code: defining community blueprints so individual actors can interact.", "economics": "Analogy: A Class is a franchise business model (like the concept of a cooperative bakery). An Object is the actual physical bakery in your neighborhood, instantiated from that model.", "techconcept": "We use the `class` keyword to define blueprints. Inside a class, `__init__` is the constructor method that sets up the initial state of the object, and `self` refers to the specific actor performing the script.", "lesson": "Mastering OOP is essential for building large-scale architecture. It allows us to group data (attributes) and behavior (methods) into logical, cooperative unitsโ€”just like the `Player` class running this very game.", "challenge": "What keyword do you use to define an object blueprint in Python?", "hint": "It is a five-letter word that sounds like a room where students learn.", "answer": ["class"], "answertype": "exact", "honorreward": 25, "bonusfirsttry": 5 } ===== missions/mission10.json ===== { "id": "review_code_gentle_art", "number": 10, "title": "Code Review โ€” The Gentle Art", "skill": "review", "tag": "ModernTech", "philosophy": "Brazilian Jiu-Jitsu (BJJ) translates to 'the gentle art.' When we roll with our training partners, the goal is not destruction, but mutual growth. Analogy: Code review is our digital tatami mat; we test each other's code to find weaknesses before the real world does.", "economics": "Quality Assurance (QA) prevents negative externalities. Reviewing code ensures that technical debt is not offloaded onto the future community or end-users. It is an act of communal maintenance.", "techconcept": "On platforms like GitHub, we propose our code changes for review before they are merged into the main project. A good review addresses Correctness, Efficiency, Readability, and Edge Cases.", "lesson": "This is the final initiation. To be a Co-Architect, you must know how to collaborate gracefully. You must propose your changes to the community.", "challenge": "What is the two-word term for proposing a merge so peers can review your code on GitHub?", "hint": "It starts with the word 'pull'.", "answer": ["pull request", "pull requests", "pr"], "answertype": "contains", "honorreward": 30, "bonusfirsttry": 10 } ===== missions/mission_template.json ===== { "id": "your_mission_id", "number": 11, "title": "Your Mission Title", "skill": "python", "tag": "Python", "philosophy": "2-4 sentences connecting this concept to a real-world human context.", "economics": "2-3 sentences explaining resource, governance, or infrastructure implications.", "techconcept": "Plain-language technical concept with optional short example.", "lesson": "A brief learner-oriented explanation of what they should understand after this mission.", "challenge": "Clear prompt for what the learner must answer or do.", "hint": "Helpful hint shown after repeated failed attempts.", "answer": "Expected answer text", "answertype": "contains", "honorreward": 20, "bonusfirsttry": 5 } ===== missions/missions.json ===== { "missions": [ { "id": "git_system_grounding", "number": 1, "title": "System Grounding", "skill": "git", "tag": "Git", "philosophy": "Like Tai Chi, where you must feel the ground before moving, a programmer must understand their environment. 'Wu Wei' is achieved when your tools become extensions of your mind.", "economics": "Infrastructure is the basis of all macro-economic stability. Before building goods, we build roads. Here, your terminal and shell are your economic infrastructure.", "techconcept": "The terminal is your direct interface with the system. Git is your tool for interacting with the open-source global supply chain of ideas.", "lesson": "Before you write any code, you need to know where you are and how to move. The terminal lets you navigate, inspect, and control your environment. Git uses that same terminal to connect your local work to remote repositories and collaborators.", "challenge": "Type the command to clone a git repository.", "hint": "The pattern is: git clone ", "answer": "git clone", "answertype": "contains", "honorreward": 20, "bonusfirsttry": 5 }, { "id": "python_variables_lineage", "number": 2, "title": "Variables & Data Lineage", "skill": "python", "tag": "Python Core", "philosophy": "Genealogy teaches us that tracing lineage reveals identity. Analogy: A variable is not merely a box; it is a named node in a data lineage. Just as ancestral lineages carry traits forward in families, variables preserve and carry state across time in an execution scope.", "economics": "Micro-economics teaches that resources are scarce and optimization is mandatory. Memory is our system's scarcest resource. Analogy: Naming variables clearly acts as a public ledger of memory allocation, preventing 'naming inflation' (where poorly named variables clog developer cognitive capacity).", "techconcept": "In Python, variables are dynamically typed. When you assign `seed = 42`, Python allocates the value `42` in memory and points the name `seed` to it. The `=` operator does not mean equality (like in math); it represents assignment, setting the flow of data.", "lesson": "In this lesson, you will practice basic variable assignment. Remember, Python is case-sensitive, and variable names should ideally use snake_case to maintain community readability standards.", "challenge": "Write exactly this: assign the integer 42 to a variable named 'seed'.", "hint": "Create a variable named 'seed' on the left, an equals sign in the middle, and the integer 42 on the right.", "answer": [ "seed = 42", "seed=42" ], "answertype": "exact", "honorreward": 20, "bonusfirsttry": 5 }, { "id": "json_data_language", "number": 3, "title": "JSON โ€” The Data Language", "skill": "json", "tag": "JSON Data", "philosophy": "Interoperability is what cooperation looks like in code. When diverse, self-determined systems must collaborate, they need a non-coercive, neutral language to exchange state. JSON represents this shared public language.", "economics": "Analogy: Global trade requires a frictionless transactional medium. JSON acts as a universal, lightweight currency of data exchange, allowing a Python server in Estonia to instantly trade structured information with a Javascript browser in Mexico without currency exchange fees (data translation overhead).", "techconcept": "JavaScript Object Notation (JSON) is a text format built on two structures: key-value pairs (`{}`) and ordered lists (`[]`). Every key must be a double-quoted string. Unlike Python, JSON uses lowercase `true`, `false`, and `null` instead of `True`, `False`, and `None`.", "lesson": "Understanding how JSON maps to native programming data structures is critical for web services, API communications, and saving user state securely.", "challenge": "What does JSON stand for? (Type out the 3 words cleanly, no extra punctuation)", "hint": "The first word is JavaScript, followed by the word for an entity (Object), and ending with representation (Notation).", "answer": "JavaScript Object Notation", "answertype": "exact", "honorreward": 20, "bonusfirsttry": 5 }, { "id": "python_functions_logic", "number": 4, "title": "Functions & 'Jeet Kune Do' Logic", "skill": "python", "tag": "Python Core", "philosophy": "Bruce Lee's martial art, Jeet Kune Do, champions 'the style of no style'โ€”using only what works and discarding the rest with maximum efficiency. Analogy: A Python function encapsulates this logic. It is a precise, clean strike of logic that does exactly what is required with no wasted energy or syntactic clutter.", "economics": "Analogy: A function is a specialized cooperative workspace. Rather than every citizen building their own solar panels from scratch (repetitive code), the community delegates the task to a specialized workshop (a function) that takes raw materials (inputs) and yields finished utility (returns).", "techconcept": "Python functions are defined using the `def` keyword, followed by the function name, parentheses containing parameters, and a colon. Code blocks must be indented (PEP 8 recommends 4 spaces). The `return` keyword is vital; it passes computed results back to the caller.", "lesson": "In this lesson, we isolate execution logic. Functions allow us to practice the DRY principle (Don't Repeat Yourself), which prevents intellectual duplication in software ecosystems.", "challenge": "What Python keyword is used to define a function?", "hint": "It is a three-letter abbreviation of the word 'define'.", "answer": "def", "answertype": "contains", "honorreward": 20, "bonusfirsttry": 5 }, { "id": "git_journalistic_integrity", "number": 5, "title": "Git โ€” Journalistic Integrity", "skill": "git", "tag": "Git Version Control", "philosophy": "Social justice relies on historical verifiability. Without transparent tracking of changes, systems of power can silently alter agreements. Analogy: Git is our cryptographic, immutable ledger of journalistic truth, preserving the timeline of who altered the system, when, and why.", "economics": "Estonia's e-governance model achieves high institutional trust because citizens can trace exactly who accesses or changes their digital records. Analogy: Git operates on a similar mathematical foundation (Merkle trees), creating a trust infrastructure where bad faith changes are structurally impossible to hide.", "techconcept": "Git does not track file differences; it tracks snapshots of your files. When you commit, Git records a cryptographic SHA-1 hash of your current staging area. The three stages of Git are: Working Directory (modifying files) -> Staging Area (`git add`) -> Git Repository (`git commit`).", "lesson": "We will practice the foundational Git workflow. Staging your files before committing is like proofreading your article before sending it to the press.", "challenge": "What exact command stages all current modifications in your directory for a Git commit?", "hint": "It starts with 'git add' and ends with a symbol representing the current directory.", "answer": "git add .", "answertype": "exact", "honorreward": 20, "bonusfirsttry": 5 }, { "id": "git_branching_merging", "number": 6, "title": "Git โ€” Branching & Merging", "skill": "git", "tag": "Git Version Control", "philosophy": "In Wing Chun, the exercise of Chi Sao (sticky hands) teaches practitioners to remain in sensitive physical contact with their opponent, feeling changes in pressure and deflecting force dynamically. Analogy: Git branching and merging is our technical Chi Sao. It allows multiple developers to work in parallel flows of time, sensitively merging their inputs while resolving systemic merge conflicts smoothly.", "economics": "In economic development, monolithic markets crash easily. Resilience requires pluralism. Analogy: Branching is our R&D sandbox. By isolating a feature branch, we allow safe experimentation without risking the stability of our primary economy (the `main` branch).", "techconcept": "A branch in Git is simply a lightweight, movable pointer to a specific commit. Creating a branch is fast and consumes virtually zero disk space because Git only records the new pointer, not a full duplicate copy of your files.", "lesson": "In this lesson, you will practice creating isolated lines of development. Modern Git workflows use `git switch` or `git checkout` to move between these lines of code safely.", "challenge": "What command would you type to create and switch to a new branch named 'feature'?", "hint": "You can use 'git checkout' with a specific flag, or the modern 'git switch' with its creation flag.", "answer": [ "git checkout -b feature", "git switch -c feature" ], "answertype": "exact", "honorreward": 25, "bonusfirsttry": 5 }, { "id": "architecture_apis_http", "number": 7, "title": "APIs & HTTP โ€” The Digital Journalist", "skill": "architecture", "tag": "Python+JSON", "philosophy": "Investigative journalism relies on primary sources. To hold systems accountable, we cannot wait to be spoon-fed data; we must query the source directly. Analogy: APIs act as the Freedom of Information Act (FOIA) of the digital world.", "economics": "Information asymmetry causes market failure. Open APIs democratize data, allowing independent actors to build tools that correct corporate or governmental monopolies on knowledge.", "techconcept": "An API (Application Programming Interface) allows two programs to talk. In Python, we use the `requests` library to GET data from URLs over HTTP. A 200 status code means success; a 404 means not found.", "lesson": "You will practice querying a system. Knowing how to request data programmatically is the foundational skill for building web scrapers, automation bots, and data journalism pipelines.", "challenge": "What standard Python library is used to make HTTP requests to web APIs?", "hint": "It is a plural word meaning 'to ask for things'.", "answer": [ "requests", "import requests" ], "answertype": "contains", "honorreward": 25, "bonusfirsttry": 5 }, { "id": "python_file_io", "number": 8, "title": "File I/O โ€” Institutional Memory", "skill": "python", "tag": "Python+JSON", "philosophy": "The Bushido code values legacy. Without memory, a society cannot learn or grow. Analogy: Writing data to a file ensures that your digital actions echo into the future, much like Estonia's resilient national data registry.", "economics": "Capital accumulation requires secure storage. If a program loses all its data when it closes, it cannot generate generational wealth. Analogy: File I/O serves as the banking system of our program.", "techconcept": "Python uses `open('file.txt', 'w')` to write to a file, and `'r'` to read. Using the `with` keyword creates a 'context manager'โ€”it ensures the file is safely closed when we are done, preventing memory leaks and data corruption.", "lesson": "We will test your understanding of context managers. Safe File I/O is exactly how our Dojo OS saves your honor points, practice chain, and journal entries behind the scenes!", "challenge": "What Python keyword is used to open a file and ensure it closes automatically?", "hint": "It is a four-letter word that pairs with 'open'.", "answer": [ "with" ], "answertype": "exact", "honorreward": 20, "bonusfirsttry": 5 }, { "id": "architecture_oop_modeling", "number": 9, "title": "OOP โ€” Sociological Modeling", "skill": "architecture", "tag": "Python OOP", "philosophy": "Theatre teaches us that a play requires Roles (scripts/blueprints) and Actors (individuals performing the role). Analogy: Object-Oriented Programming (OOP) applies sociology to code: defining community blueprints so individual actors can interact.", "economics": "Analogy: A Class is a franchise business model (like the concept of a cooperative bakery). An Object is the actual physical bakery in your neighborhood, instantiated from that model.", "techconcept": "We use the `class` keyword to define blueprints. Inside a class, `__init__` is the constructor method that sets up the initial state of the object, and `self` refers to the specific actor performing the script.", "lesson": "Mastering OOP is essential for building large-scale architecture. It allows us to group data (attributes) and behavior (methods) into logical, cooperative unitsโ€”just like the `Player` class running this very game.", "challenge": "What keyword do you use to define an object blueprint in Python?", "hint": "It is a five-letter word that sounds like a room where students learn.", "answer": [ "class" ], "answertype": "exact", "honorreward": 25, "bonusfirsttry": 5 }, { "id": "review_code_gentle_art", "number": 10, "title": "Code Review โ€” The Gentle Art", "skill": "review", "tag": "ModernTech", "philosophy": "Brazilian Jiu-Jitsu (BJJ) translates to 'the gentle art.' When we roll with our training partners, the goal is not destruction, but mutual growth. Analogy: Code review is our digital tatami mat; we test each other's code to find weaknesses before the real world does.", "economics": "Quality Assurance (QA) prevents negative externalities. Reviewing code ensures that technical debt is not offloaded onto the future community or end-users. It is an act of communal maintenance.", "techconcept": "On platforms like GitHub, we propose our code changes for review before they are merged into the main project. A good review addresses Correctness, Efficiency, Readability, and Edge Cases.", "lesson": "This is the final initiation. To be a Co-Architect, you must know how to collaborate gracefully. You must propose your changes to the community.", "challenge": "What is the two-word term for proposing a merge so peers can review your code on GitHub?", "hint": "It starts with the word 'pull'.", "answer": [ "pull request", "pull requests", "pr" ], "answertype": "contains", "honorreward": 30, "bonusfirsttry": 10 } ] } ===== newcomer.py ===== #!/usr/bin/env python3 """One-command onboarding for contributors.""" from __future__ import annotations import argparse import subprocess import sys from pathlib import Path REPO_ROOT = Path(__file__).resolve().parent def run_step(command: list[str], label: str) -> None: print(f"\n==> {label}") subprocess.run(command, cwd=REPO_ROOT, check=True) def main() -> int: parser = argparse.ArgumentParser(description="Run Dojo contributor onboarding checks.") parser.add_argument( "--play", action="store_true", help="Launch dojo_classroom.py after setup and checks.", ) args = parser.parse_args() try: run_step([sys.executable, "-m", "pip", "install", "--upgrade", "pip"], "Upgrade pip") run_step([sys.executable, "-m", "pip", "install", "-r", "requirements.txt"], "Install dependencies") run_step([sys.executable, "-m", "unittest", "discover", "-s", "tests"], "Run tests") run_step([sys.executable, "validate_missions.py"], "Validate missions") if args.play: run_step([sys.executable, "dojo_classroom.py"], "Launch Dojo classroom") else: print("\nโœ… Onboarding checks passed. Run: python newcomer.py --play") except subprocess.CalledProcessError as exc: print(f"\nโŒ Failed during: {exc}") return exc.returncode return 0 if __name__ == "__main__": raise SystemExit(main()) ===== refresh_repo_bundle.py ===== #!/usr/bin/env python3 """Convenience wrapper to regenerate the plain-text repo bundle.""" from pathlib import Path import sys from repo_text_export import generate_repo_text_bundle def main() -> int: root = Path(__file__).resolve().parent output_path = root / "repo_bundle.txt" generate_repo_text_bundle(root, output_path) print(f"Refreshed {output_path}") return 0 if __name__ == "__main__": sys.exit(main()) ===== repo_text_export.py ===== #!/usr/bin/env python3 """Create a plain-text bundle of the repository contents for AI collaborators.""" from __future__ import annotations from pathlib import Path import sys IGNORE_DIRS = {".git", ".venv", "__pycache__", ".pytest_cache", "node_modules"} IGNORE_FILES = {".DS_Store", "Thumbs.db"} GENERATED_BUNDLE_FILES = { "repo_bundle.txt", "bundle_missions.txt", "bundle_docs.txt", "bundle_code.txt", } def iter_repo_files(root: Path): for path in sorted(root.rglob("*")): if not path.is_file(): continue rel = path.relative_to(root) parts = rel.parts if any(part in IGNORE_DIRS for part in parts[:-1]): continue if any(part in IGNORE_DIRS for part in parts): continue if rel.parts and rel.parts[0] in IGNORE_DIRS: continue if path.name in IGNORE_FILES: continue if path.name in GENERATED_BUNDLE_FILES: continue yield path def generate_repo_text_bundle(root: Path, output_path: Path) -> Path: root = root.resolve() output_path = output_path.resolve() lines = [] for path in iter_repo_files(root): rel = path.relative_to(root) lines.append(f"===== {rel} =====") try: content = path.read_text(encoding="utf-8") except UnicodeDecodeError: content = "" lines.append(content.rstrip()) lines.append("") output_path.write_text("\n".join(lines), encoding="utf-8") return output_path def main() -> int: root = Path(sys.argv[1]).resolve() if len(sys.argv) > 1 else Path.cwd() output_path = Path(sys.argv[2]).resolve() if len(sys.argv) > 2 else root / "repo_bundle.txt" generate_repo_text_bundle(root, output_path) print(f"Wrote {output_path}") return 0 if __name__ == "__main__": main() ===== requirements.txt ===== # DOJO ASCENSION v4.0 โ€” Python dependencies # Install with: pip install -r requirements.txt colorama>=0.4.6 # Colored terminal output (cross-platform) requests>=2.31.0 # HTTP requests for API missions # All other modules (json, os, pathlib, subprocess) are Python stdlib ===== tests/dojo.py ===== class Engine: def __init__(self, player): self.player = player self.missions = self.load_missions() def load_missions(self): missions = [] if not os.path.exists(MISSIONS_DIR): os.makedirs(MISSIONS_DIR) console.print(f"[yellow]Created {MISSIONS_DIR}/ directory. Please add mission JSON files.[/yellow]") return missions for filename in sorted(os.listdir(MISSIONS_DIR)): if filename.endswith(".json"): with open(os.path.join(MISSIONS_DIR, filename), 'r') as f: missions.append(json.load(f)) return sorted(missions, key=lambda x: x.get("number", 999)) def run_mission(self, mission): console.clear() console.print(Panel(f"[bold cyan]{mission['title']}[/bold cyan]", expand=False)) # Progressive Disclosure: Beginner vs Deep Dive console.print(f"\n[bold green]๐ŸŒฑ Philosophy (Beginner):[/bold green] {mission['philosophy_beginner']}") if Confirm.ask("\n[bold]Engage Deep Dive?[/bold] (Explore Uta Hagen, Economics, and Systems Thinking)"): deep_dive_text = f"**Philosophy:** {mission['philosophy_deep_dive']}\n\n**Economics:** {mission['economics']}" console.print(Panel(Markdown(deep_dive_text), border_style="blue")) console.print(f"\n[bold magenta]๐Ÿ’ป Technical Concept:[/bold magenta] {mission['tech_concept']}") console.print(f"\n[bold red]โš”๏ธ Challenge:[/bold red] {mission['challenge']}") # Action-Based Challenge with Retry Loop & Forgiving Parsing while True: attempt = Prompt.ask("\n[bold yellow]Enter your code (or type 'retreat')[/bold yellow]") if attempt.strip().lower() == 'retreat': console.print("[dim]Retreating to the Dojo hub. Re-evaluate your strategy.[/dim]") break # Normalize whitespaces to prevent frustration over a single space normalized_attempt = " ".join(attempt.strip().split()) normalized_answers = [" ".join(a.strip().split()) for a in mission['answer']] if normalized_attempt in normalized_answers: console.print("\n[bold green]โœ… Ecology sustained. Mission passed.[/bold green]") self.award_competency(mission) self.uta_hagen_reflection(mission) break else: console.print("[bold red]โŒ Syntax misalignment. The ecosystem rejects this input. Try again.[/bold red]") def award_competency(self, mission): skill = mission.get("skill_target", "python") # Ensure we don't hit a KeyError if a community member adds a rogue skill if skill in self.player.competencies: self.player.competencies[skill] += 1 self.player.honor += 10 self.player.completed_missions.append(mission["id"]) console.print(f"[green]+10 Honor | +1 {skill.capitalize()} Competency[/green]") def uta_hagen_reflection(self, mission): console.print("\n[bold blue]๐Ÿ“” Uta Hagen Systems Thinking Journal[/bold blue]") reflection = Prompt.ask("What part of this system surprised you, or what remains confusing? (Persisted to ledger)") entry = { "mission_id": mission["id"], "reflection": reflection } self.player.journal.append(entry) self.player.save_state() console.print("[dim]Journal serialized to save_state.json.[/dim]") def start(self): # Persistent Hub Loop while True: console.clear() console.print(Panel("[bold green]Welcome to DOJO ASCENSION[/bold green]\n[dim]Initializing Digital Ecology...[/dim]")) self.player.display_stats() # Refresh available missions on every loop iteration available_missions = [m for m in self.missions if m["id"] not in self.player.completed_missions] if not available_missions: console.print("\n[bold cyan]All available ecosystems restored. Await new JSON packs from the community.[/bold cyan]") break next_mission = available_missions[0] console.print("\n[bold]Dojo Options:[/bold]") console.print(f"1. Engage Mission {next_mission['number']}: {next_mission['title']}") console.print("2. Exit Dojo (Save State)") choice = Prompt.ask("\nSelect an action", choices=["1", "2"]) if choice == "2": console.print("[dim]Suspending digital ecology... Farewell.[/dim]") break # Execute mission, then pause before resetting the loop self.run_mission(next_mission) Prompt.ask("\n[dim]Press Enter to return to the Dojo Hub...[/dim]") if __name__ == "__main__": player = Player() engine = Engine(player) engine.start() ===== tests/test_chronicle_tips.py ===== """Unit tests for the /chronicle tips feature in dojo_classroom.py.""" import io import os import tempfile import unittest from datetime import datetime, timedelta from pathlib import Path from unittest.mock import patch import dojo_classroom from dojo_classroom import Player, chronicle_tips, get_practice_chain # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ # Helpers # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ def _make_player(honor=0, completed=None, skills=None): """Return a Player with no disk I/O side-effects.""" with tempfile.TemporaryDirectory() as tmpdir: os.environ["DOJO_DATA_DIR"] = tmpdir os.environ["DOJO_SAVE_FILE"] = str(Path(tmpdir) / "save.json") os.environ["DOJO_JOURNAL_FILE"] = str(Path(tmpdir) / "journal.json") dojo_classroom.SAVE_FILE, dojo_classroom.JOURNAL_FILE = \ dojo_classroom.get_state_paths() p = Player("Tester", honor=honor, completed=completed, skills=skills) return p def _minimal_missions(n=3): """Return *n* lightweight mission dicts.""" skills = ["git", "python", "json"] return [ { "id": f"mission_{i}", "number": i + 1, "title": f"Mission {i + 1}", "skill": skills[i % len(skills)], } for i in range(n) ] def _journal_with_entries(dates): """Return journal data dict keyed by date strings.""" return { date: { "timestamp": f"{date}T10:00:00", "mission_id": "demo", "mission_title": "Demo", "player_rank": "Initiate", "answers": {"Q1": "An answer", "Q2": "Another answer"}, } for date in dates } def _capture_chronicle_tips(player, missions, journal_data): """Run chronicle_tips and return printed output as a string.""" buf = io.StringIO() # Suppress clear_screen, header, divider, wait for unit testing with patch("dojo_classroom.clear_screen"), \ patch("dojo_classroom.header"), \ patch("dojo_classroom.divider"), \ patch("dojo_classroom.wait"), \ patch("sys.stdout", buf): chronicle_tips(player, missions, journal_data) return buf.getvalue() # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ # Tests: journal-related tips # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ class TestChronicleJournalTips(unittest.TestCase): def test_no_journal_entries_surfaces_start_journal_tip(self): player = _make_player() missions = _minimal_missions() output = _capture_chronicle_tips(player, missions, {}) self.assertIn("Start your reflection journal", output) def test_zero_chain_but_past_entries_surfaces_rebuild_tip(self): # Entry from 3 days ago โ€” no entry yesterday/today โ†’ chain = 0 old_date = (datetime.now() - timedelta(days=3)).strftime("%Y-%m-%d") journal = _journal_with_entries([old_date]) player = _make_player() missions = _minimal_missions() output = _capture_chronicle_tips(player, missions, journal) self.assertIn("Rebuild your practice chain", output) def test_active_chain_surfaces_chain_length(self): # Entries for today and yesterday โ†’ chain = 2 today = datetime.now().strftime("%Y-%m-%d") yesterday = (datetime.now() - timedelta(days=1)).strftime("%Y-%m-%d") journal = _journal_with_entries([today, yesterday]) player = _make_player() missions = _minimal_missions() output = _capture_chronicle_tips(player, missions, journal) self.assertIn("2 day", output) def test_short_reflection_answers_surfaces_depth_nudge(self): today = datetime.now().strftime("%Y-%m-%d") journal = { today: { "timestamp": f"{today}T10:00:00", "mission_id": "demo", "mission_title": "Demo", "player_rank": "Initiate", "answers": {"Q1": "ok", "Q2": "yes"}, # very short } } player = _make_player() missions = _minimal_missions() output = _capture_chronicle_tips(player, missions, journal) self.assertIn("deeper in your reflections", output) # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ # Tests: mission-completion tips # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ class TestChronicleMissionTips(unittest.TestCase): def test_no_completed_missions_surfaces_begin_tip(self): player = _make_player() missions = _minimal_missions() output = _capture_chronicle_tips(player, missions, {}) self.assertIn("Begin your first mission", output) def test_all_missions_complete_surfaces_go_deeper_tip(self): missions = _minimal_missions(3) completed_ids = [m["id"] for m in missions] player = _make_player(honor=100, completed=completed_ids) output = _capture_chronicle_tips(player, missions, {}) self.assertIn("All missions complete", output) def test_partial_completion_surfaces_next_mission(self): missions = _minimal_missions(3) # Complete only the first mission player = _make_player(honor=20, completed=[missions[0]["id"]]) output = _capture_chronicle_tips(player, missions, {}) self.assertIn("Mission 2", output) # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ # Tests: skill-gap tips # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ class TestChronicleSkillTips(unittest.TestCase): def test_zero_skill_surfaces_untouched_tip(self): skills = {"git": 0, "python": 3, "json": 2, "architecture": 1, "review": 1} player = _make_player(skills=skills) missions = _minimal_missions() output = _capture_chronicle_tips(player, missions, {}) self.assertIn("Untouched skill", output) self.assertIn("GIT", output) def test_large_skill_gap_surfaces_balance_tip(self): skills = {"git": 1, "python": 4, "json": 1, "architecture": 1, "review": 1} player = _make_player(skills=skills) missions = _minimal_missions() output = _capture_chronicle_tips(player, missions, {}) self.assertIn("Balance your skills", output) def test_balanced_skills_surfaces_balanced_tip(self): skills = {"git": 3, "python": 3, "json": 3, "architecture": 3, "review": 3} player = _make_player(skills=skills) missions = _minimal_missions() output = _capture_chronicle_tips(player, missions, {}) self.assertIn("balanced", output) # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ # Tests: honor/rank tips # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ class TestChronicleHonorTips(unittest.TestCase): def test_honor_gap_surfaces_rank_progress_tip(self): player = _make_player(honor=10) missions = _minimal_missions() output = _capture_chronicle_tips(player, missions, {}) self.assertIn("honor until rank", output) def test_max_honor_no_rank_tip(self): # 1000+ honor โ†’ Co-Architect, no next rank player = _make_player(honor=1001) missions = _minimal_missions(3) completed_ids = [m["id"] for m in missions] player.completed = set(completed_ids) missions_all_done = missions output = _capture_chronicle_tips(player, missions_all_done, {}) self.assertNotIn("honor until rank", output) # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ # Tests: slash-command routing in game_loop # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ class TestChronicleMenuDispatch(unittest.TestCase): """Verify that /chronicle tips and /chronicle are dispatched correctly.""" def _run_one_loop_iteration(self, menu_input): """ Patch main_menu to return *menu_input* once then '7' to exit. Assert chronicle_tips is called exactly once. """ missions = _minimal_missions() call_count = {"n": 0} def fake_chronicle_tips(p, m, j): call_count["n"] += 1 menu_responses = iter([menu_input, '7']) with tempfile.TemporaryDirectory() as tmpdir: save_path = Path(tmpdir) / "save.json" journal_path = Path(tmpdir) / "journal.json" save_path.write_text( '{"name":"Tester","honor":10,"completed":[],' '"skills":{"git":1,"python":1,"json":1,' '"architecture":0,"review":0}}', encoding="utf-8" ) with patch("dojo_classroom.load_missions", return_value=missions), \ patch("dojo_classroom.get_state_paths", return_value=(save_path, journal_path)), \ patch("dojo_classroom.SAVE_FILE", save_path), \ patch("dojo_classroom.JOURNAL_FILE", journal_path), \ patch("dojo_classroom.load_journal_data", return_value={}), \ patch("dojo_classroom.chronicle_tips", side_effect=fake_chronicle_tips), \ patch("dojo_classroom.main_menu", side_effect=lambda p, m: next(menu_responses)), \ patch("dojo_classroom.clear_screen"), \ patch("dojo_classroom.print_slow"), \ patch("dojo_classroom.wait"), \ self.assertRaises(SystemExit): dojo_classroom.game_loop() return call_count["n"] def test_slash_chronicle_tips_dispatches(self): n = self._run_one_loop_iteration('/chronicle tips') self.assertEqual(n, 1) def test_slash_chronicle_alone_dispatches(self): n = self._run_one_loop_iteration('/chronicle') self.assertEqual(n, 1) def test_slash_chronicle_tips_case_insensitive(self): n = self._run_one_loop_iteration('/Chronicle Tips') self.assertEqual(n, 1) if __name__ == "__main__": unittest.main() ===== tests/test_impact_metrics.py ===== import json import os import tempfile import unittest from datetime import datetime, timedelta from pathlib import Path from unittest.mock import patch import dojo_classroom from dojo_classroom import Player, execute_mission from export_learner_data import aggregate, load_learner_records class ImpactMetricTests(unittest.TestCase): def setUp(self): self.tempdir = tempfile.TemporaryDirectory() self.addCleanup(self.tempdir.cleanup) tmp = Path(self.tempdir.name) os.environ["DOJO_DATA_DIR"] = str(tmp) os.environ["DOJO_SAVE_FILE"] = str(tmp / "dojo_save.json") os.environ["DOJO_JOURNAL_FILE"] = str(tmp / "dojo_journal_data.json") self.addCleanup(os.environ.pop, "DOJO_DATA_DIR", None) self.addCleanup(os.environ.pop, "DOJO_SAVE_FILE", None) self.addCleanup(os.environ.pop, "DOJO_JOURNAL_FILE", None) dojo_classroom.SAVE_FILE, dojo_classroom.JOURNAL_FILE = dojo_classroom.get_state_paths() def test_player_state_tracks_sessions_for_retention(self): first = Player("Tester") first_count = first.session_count first_log_length = len(first.session_log) second = Player("Tester") self.assertGreaterEqual(first_count, 1) self.assertEqual(first_log_length, first_count) self.assertEqual(second.session_count, first_count + 1) self.assertEqual(len(second.session_log), second.session_count) self.assertIsNotNone(second.first_session_at) self.assertIsNotNone(second.last_session_at) def test_replaying_mission_does_not_double_award_honor(self): mission = { "id": "mission_one", "number": 1, "title": "Mission One", "philosophy": "Anchor", "economics": "Economics", "lesson": "Lesson", "challenge": "Challenge", "answer": "ok", "skill": "python", "honor_base": 20, } player = Player("Tester") with patch("dojo_classroom.wait"), \ patch("dojo_classroom.run_code_challenge", return_value=True), \ patch("dojo_classroom.journal_reflection"), \ patch("dojo_classroom.load_missions", return_value=[mission]): execute_mission(mission, player, [mission]) honor_after_first = player.honor execute_mission(mission, player, [mission]) self.assertEqual(honor_after_first, 20) self.assertEqual(player.honor, honor_after_first) self.assertEqual(player.skills["python"], 1) self.assertEqual(len(player.completed), 1) def test_export_learner_data_reports_retention(self): save_path = Path(self.tempdir.name) / "cohort" / "dojo_save.json" save_path.parent.mkdir(parents=True, exist_ok=True) start = datetime(2026, 7, 1, 12, 0, 0) return_visit = start + timedelta(days=5) save_path.write_text( json.dumps( { "name": "Learner", "honor": 40, "completed": ["m1", "m2"], "skills": {"python": 1, "git": 1, "json": 0, "architecture": 0, "review": 0}, "first_session_at": start.isoformat(), "last_session_at": return_visit.isoformat(), "last_mission_at": return_visit.isoformat(), "session_count": 2, "session_log": [start.isoformat(), return_visit.isoformat()], } ), encoding="utf-8", ) records = load_learner_records(Path(self.tempdir.name)) summary = aggregate(records) self.assertEqual(len(records), 1) self.assertTrue(records[0]["returned_within_14_days"]) self.assertEqual(summary["learner_count"], 1) self.assertEqual(summary["retention_rate_percent"], 100.0) if __name__ == "__main__": unittest.main() ===== tests/test_repo_text_export.py ===== import tempfile import unittest from pathlib import Path import repo_text_export class RepoTextExportTests(unittest.TestCase): def test_creates_plain_text_bundle_without_git_metadata(self): with tempfile.TemporaryDirectory() as tmpdir: root = Path(tmpdir) (root / "README.md").write_text("hello world\n", encoding="utf-8") (root / "notes.txt").write_text("more text\n", encoding="utf-8") (root / ".git").mkdir() (root / ".git" / "config").write_text("ignore me\n", encoding="utf-8") output_path = root / "repo_bundle.txt" repo_text_export.generate_repo_text_bundle(root, output_path) content = output_path.read_text(encoding="utf-8") self.assertIn("README.md", content) self.assertIn("hello world", content) self.assertIn("notes.txt", content) self.assertNotIn(".git", content) self.assertNotIn("ignore me", content) if __name__ == "__main__": unittest.main() ===== tests/test_state_paths.py ===== import json import os import tempfile import unittest from pathlib import Path import dojo_classroom class StatePathTests(unittest.TestCase): def test_default_missions_file_points_to_repo_index(self): expected = Path(dojo_classroom.__file__).resolve().parent / "missions" / "missions.json" self.assertEqual(dojo_classroom.MISSIONS_FILE.resolve(), expected) def test_load_missions_falls_back_to_folder_files_when_index_is_missing(self): with tempfile.TemporaryDirectory() as tmpdir: mission_dir = Path(tmpdir) / "missions" mission_dir.mkdir() mission_path = mission_dir / "mission01.json" mission_path.write_text( json.dumps({ "id": "demo_mission", "number": 1, "title": "Demo Mission", "skill": "python", "challenge": "What is 2 + 2?", "answer": "4" }), encoding="utf-8" ) original_missions_file = dojo_classroom.MISSIONS_FILE try: dojo_classroom.MISSIONS_FILE = mission_dir / "missions.json" missions = dojo_classroom.load_missions() finally: dojo_classroom.MISSIONS_FILE = original_missions_file self.assertEqual(len(missions), 1) self.assertEqual(missions[0]["id"], "demo_mission") def test_state_paths_follow_environment_overrides(self): with tempfile.TemporaryDirectory() as tmpdir: custom_dir = Path(tmpdir) / "alice-dojo" save_path = custom_dir / "player-save.json" journal_path = custom_dir / "player-journal.json" os.environ["DOJO_DATA_DIR"] = str(custom_dir) os.environ["DOJO_SAVE_FILE"] = str(save_path) os.environ["DOJO_JOURNAL_FILE"] = str(journal_path) self.addCleanup(os.environ.pop, "DOJO_DATA_DIR", None) self.addCleanup(os.environ.pop, "DOJO_SAVE_FILE", None) self.addCleanup(os.environ.pop, "DOJO_JOURNAL_FILE", None) resolved_save, resolved_journal = dojo_classroom.get_state_paths() self.assertEqual(resolved_save, save_path) self.assertEqual(resolved_journal, journal_path) if __name__ == "__main__": unittest.main() ===== tests/test_validate_missions.py ===== import json import tempfile import unittest from pathlib import Path import validate_missions class MissionValidationTests(unittest.TestCase): def test_accepts_valid_single_mission_file(self): with tempfile.TemporaryDirectory() as tmpdir: path = Path(tmpdir) / "mission.json" path.write_text( json.dumps({ "id": "demo_mission", "number": 11, "title": "Demo Mission", "skill": "python", "challenge": "What is 2 + 2?", "answer": "4" }), encoding="utf-8" ) errors = validate_missions.validate_path(path) self.assertEqual(errors, []) def test_accepts_valid_mission_index_file(self): with tempfile.TemporaryDirectory() as tmpdir: path = Path(tmpdir) / "missions.json" path.write_text( json.dumps({ "missions": [{ "id": "demo_mission", "number": 11, "title": "Demo Mission", "skill": "python", "challenge": "What is 2 + 2?", "answer": "4" }] }), encoding="utf-8" ) errors = validate_missions.validate_path(path) self.assertEqual(errors, []) def test_accepts_folder_style_mission_file(self): with tempfile.TemporaryDirectory() as tmpdir: mission_dir = Path(tmpdir) / "missions" mission_dir.mkdir() path = mission_dir / "mission02.json" path.write_text( json.dumps({ "id": "demo_folder_mission", "number": 2, "title": "Folder Mission", "skill": "python", "tag": "Python", "philosophy": "A short philosophy note.", "economics": "A short economics note.", "techconcept": "A short tech note.", "lesson": "A short lesson note.", "challenge": "What is 2 + 2?", "hint": "Think carefully.", "answer": ["4"], "answertype": "exact", "honorreward": 20, "bonusfirsttry": 5 }), encoding="utf-8" ) errors = validate_missions.validate_path(path) self.assertEqual(errors, []) def test_reports_missing_required_fields(self): with tempfile.TemporaryDirectory() as tmpdir: path = Path(tmpdir) / "mission.json" path.write_text(json.dumps({"id": "broken"}), encoding="utf-8") errors = validate_missions.validate_path(path) self.assertTrue(any("Missing required field" in error for error in errors)) if __name__ == "__main__": unittest.main() ===== validate_missions.py ===== #!/usr/bin/env python3 """Tiny mission validation helper for contributors.""" import json import sys from pathlib import Path REQUIRED_FIELDS = [ "id", "number", "title", "skill", "challenge", "answer", ] FOLDER_SCHEMA_FIELDS = [ "id", "number", "title", "skill", "tag", "philosophy", "economics", "techconcept", "lesson", "challenge", "hint", "answer", "answertype", "honorreward", "bonusfirsttry", ] def describe_fix(errors): """Return a short, human-friendly summary of what to fix.""" if not errors: return "No issues found." if any("Missing required field" in error for error in errors): return "Add the missing required fields and keep the mission schema consistent." if any("must be" in error for error in errors): return "Check the field types so each value matches the expected format." if any("should be named" in error for error in errors): return "Rename the file to match the repository's mission naming convention." return "Review the listed issues and adjust the mission metadata accordingly." def validate_path(path): """Validate a single mission file or a mission index file.""" path = Path(path) errors = [] if not path.exists(): return [f"File not found: {path}"] if path.is_dir(): return [f"Path is a directory, not a file: {path}"] try: with path.open("r", encoding="utf-8") as handle: data = json.load(handle) except (OSError, json.JSONDecodeError) as exc: return [f"Invalid JSON: {exc}"] if isinstance(data, dict) and "missions" in data: items = data.get("missions", []) if not isinstance(items, list): return ["'missions' must be a list"] records = items if path.name != "missions.json": errors.append( f"Mission index file should be named 'missions.json', found '{path.name}'" ) elif isinstance(data, dict): records = [data] if path.parent.name == "missions": number = data.get("number") expected_name = f"mission{int(number):02d}.json" if isinstance(number, int) else "" if expected_name and path.name != expected_name: errors.append( f"Mission file should be named '{expected_name}', found '{path.name}'" ) else: return ["Mission file must contain a JSON object"] for index, mission in enumerate(records, 1): if not isinstance(mission, dict): errors.append(f"Mission #{index} is not an object") continue required_fields = REQUIRED_FIELDS if path.parent.name == "missions" and path.name != "missions.json": required_fields = FOLDER_SCHEMA_FIELDS for field in required_fields: if field not in mission: errors.append( f"Mission #{index}: Missing required field '{field}'" ) if "number" in mission and not isinstance(mission["number"], int): errors.append(f"Mission #{index}: 'number' must be an integer") if "id" in mission and not isinstance(mission["id"], str): errors.append(f"Mission #{index}: 'id' must be a string") if "title" in mission and not isinstance(mission["title"], str): errors.append(f"Mission #{index}: 'title' must be a string") if "skill" in mission and not isinstance(mission["skill"], str): errors.append(f"Mission #{index}: 'skill' must be a string") if "challenge" in mission and not isinstance(mission["challenge"], str): errors.append(f"Mission #{index}: 'challenge' must be a string") if "answer" in mission: if not isinstance(mission["answer"], (str, list)): errors.append(f"Mission #{index}: 'answer' must be a string or list") if "answertype" in mission and not isinstance(mission["answertype"], str): errors.append(f"Mission #{index}: 'answertype' must be a string") if "honorreward" in mission and not isinstance(mission["honorreward"], int): errors.append(f"Mission #{index}: 'honorreward' must be an integer") return errors def main(): """CLI entry point for validating mission files.""" targets = sys.argv[1:] if not targets: mission_dir = Path("missions") targets = [ str(mission_dir / f"mission{index:02d}.json") for index in range(1, 11) ] + [str(mission_dir / "missions.json")] all_errors = [] for target in targets: errors = validate_path(target) if errors: print(f"[FAIL] {target}") print(f" What to fix: {describe_fix(errors)}") for error in errors: print(f" - {error}") all_errors.extend(errors) else: print(f"[OK] {target}") if all_errors: sys.exit(1) if __name__ == "__main__": main()