diff --git a/learning-room/.github/workflows/autograder-capstone.yml b/learning-room/.github/workflows/autograder-capstone.yml index e69de29..a58111c 100644 --- a/learning-room/.github/workflows/autograder-capstone.yml +++ b/learning-room/.github/workflows/autograder-capstone.yml @@ -0,0 +1,117 @@ +name: "Challenge 16: Capstone Agent File Validation" +# Validates that the student's agent file has valid YAML frontmatter, +# responsibilities, and guardrails sections +# NOTE: This workflow runs in the accessibility-agents repo, not learning-room + +on: + pull_request: + types: [opened, synchronize, reopened] + paths: + - "agents/**/*.md" + - "community-agents/**/*.md" + +permissions: + contents: read + pull-requests: write + +jobs: + validate-agent: + name: Verify Agent File + runs-on: ubuntu-latest + + steps: + - name: Checkout PR branch + uses: actions/checkout@v4 + + - name: Find and validate agent files + id: check + run: | + # Find new/modified .md files in agents/ or community-agents/ + AGENTS=$(find agents community-agents -name '*.md' 2>/dev/null || true) + + if [ -z "$AGENTS" ]; then + echo "found=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + + echo "found=true" >> "$GITHUB_OUTPUT" + FIRST=$(echo "$AGENTS" | head -1) + echo "file=$FIRST" >> "$GITHUB_OUTPUT" + CHECK_FILE="/tmp/capstone-agent.md" + sed '1s/^\xEF\xBB\xBF//' "$FIRST" > "$CHECK_FILE" + + ERRORS="" + + # Check YAML frontmatter exists (starts with ---) + if head -1 "$CHECK_FILE" | grep -q '^---'; then + echo "has_frontmatter=true" >> "$GITHUB_OUTPUT" + else + echo "has_frontmatter=false" >> "$GITHUB_OUTPUT" + ERRORS="missing YAML frontmatter" + fi + + # Check for responsibilities section + if grep -qi '## responsibilities\|## what this agent does' "$CHECK_FILE"; then + echo "has_responsibilities=true" >> "$GITHUB_OUTPUT" + else + echo "has_responsibilities=false" >> "$GITHUB_OUTPUT" + ERRORS="$ERRORS${ERRORS:+, }missing responsibilities section" + fi + + # Check for guardrails section + if grep -qi '## guardrails\|## limitations\|## boundaries' "$CHECK_FILE"; then + echo "has_guardrails=true" >> "$GITHUB_OUTPUT" + else + echo "has_guardrails=false" >> "$GITHUB_OUTPUT" + ERRORS="$ERRORS${ERRORS:+, }missing guardrails section" + fi + + echo "errors=$ERRORS" >> "$GITHUB_OUTPUT" + + - name: Post result + if: always() + uses: actions/github-script@v7 + with: + script: | + const found = '${{ steps.check.outputs.found }}' === 'true'; + const file = '${{ steps.check.outputs.file }}'; + const errors = '${{ steps.check.outputs.errors }}'; + let body; + if (!found) { + body = [ + '## Challenge 16: No agent file found', + '', + 'Create an `.md` file in the `agents/` or `community-agents/` directory ', + 'with YAML frontmatter, a responsibilities section, and a guardrails section.', + '', + 'See [Chapter 20: Build Your Agent](https://github.com/Community-Access/git-going-with-github/blob/main/docs/20-build-your-agent.md) for the template.' + ].join('\n'); + } else if (errors) { + body = [ + '## Challenge 16: Agent file needs work', + '', + 'Found `' + file + '` but it has issues: ' + errors + '.', + '', + 'Your agent file should have:', + '- YAML frontmatter (between `---` markers)', + '- A responsibilities/purpose section', + '- A guardrails/limitations section', + ].join('\n'); + } else { + body = [ + '## Challenge 16: Agent file looks great!', + '', + 'Found `' + file + '` with valid frontmatter, responsibilities, and guardrails. ', + 'Excellent capstone work!' + ].join('\n'); + } + try { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body + }); + } catch (error) { + console.error('Could not post Challenge 16 result:', error.message); + } diff --git a/learning-room/.github/workflows/autograder-conflicts.yml b/learning-room/.github/workflows/autograder-conflicts.yml index e69de29..1749172 100644 --- a/learning-room/.github/workflows/autograder-conflicts.yml +++ b/learning-room/.github/workflows/autograder-conflicts.yml @@ -0,0 +1,87 @@ +name: "Challenge 7: Merge Conflict Resolution Check" +# Validates that the student resolved all merge conflict markers + +on: + pull_request: + types: [opened, synchronize, reopened] + paths: + - "docs/welcome.md" + +permissions: + contents: read + pull-requests: write + +jobs: + check-conflict-markers: + name: Verify Conflict Resolution + runs-on: ubuntu-latest + + steps: + - name: Checkout PR branch + uses: actions/checkout@v4 + + - name: Check for conflict markers + id: check + run: | + MARKERS=$(grep -rn '<<<<<<< \|======= \|>>>>>>> ' docs/ || true) + if [ -n "$MARKERS" ]; then + echo "found=true" >> "$GITHUB_OUTPUT" + echo "$MARKERS" > /tmp/markers.txt + else + echo "found=false" >> "$GITHUB_OUTPUT" + fi + + - name: Check for substantive content + id: content + run: | + LINES=$(wc -l < docs/welcome.md) + if [ "$LINES" -lt 5 ]; then + echo "empty=true" >> "$GITHUB_OUTPUT" + else + echo "empty=false" >> "$GITHUB_OUTPUT" + fi + + - name: Post result + if: always() + uses: actions/github-script@v7 + with: + script: | + const hasMarkers = '${{ steps.check.outputs.found }}' === 'true'; + const isEmpty = '${{ steps.content.outputs.empty }}' === 'true'; + let body; + if (hasMarkers) { + body = [ + '## Challenge 7: Conflict markers still present', + '', + 'Your PR still contains Git conflict markers (`<<<<<<<`, `=======`, `>>>>>>>`). ', + 'These need to be removed as part of resolving the merge conflict.', + '', + '**What to do:** Open the file, find the conflict markers, decide which lines to keep, ', + 'and delete the marker lines. Then commit and push again.', + '', + 'See [Chapter 7: Merge Conflicts](https://github.com/Community-Access/git-going-with-github/blob/main/docs/07-merge-conflicts.md) for help.' + ].join('\n'); + } else if (isEmpty) { + body = [ + '## Challenge 7: File looks empty', + '', + 'The conflict was resolved but the file has very little content. ', + 'Make sure you kept the lines you intended to keep.', + ].join('\n'); + } else { + body = [ + '## Challenge 7: Conflict resolution looks good!', + '', + 'No conflict markers found and the file has substantive content. Well done!' + ].join('\n'); + } + try { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body + }); + } catch (error) { + console.error('Could not post Challenge 7 result:', error.message); + } diff --git a/learning-room/.github/workflows/autograder-local-commit.yml b/learning-room/.github/workflows/autograder-local-commit.yml index e69de29..a3e7f51 100644 --- a/learning-room/.github/workflows/autograder-local-commit.yml +++ b/learning-room/.github/workflows/autograder-local-commit.yml @@ -0,0 +1,78 @@ +name: "Challenge 10: Local Commit Check" +# Validates that the student made at least one commit on a non-default branch + +on: + pull_request: + types: [opened, synchronize, reopened] + branches: + - main + +permissions: + contents: read + pull-requests: write + +jobs: + check-local-commit: + name: Verify Local Commit Exists + runs-on: ubuntu-latest + + steps: + - name: Checkout full history + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Check branch and commits + id: check + run: | + BRANCH="${{ github.head_ref }}" + if [ "$BRANCH" = "main" ]; then + echo "on_main=true" >> "$GITHUB_OUTPUT" + else + echo "on_main=false" >> "$GITHUB_OUTPUT" + fi + COMMITS=$(git log --oneline origin/main..HEAD | wc -l) + echo "count=$COMMITS" >> "$GITHUB_OUTPUT" + + - name: Post result + if: always() + uses: actions/github-script@v7 + with: + script: | + const onMain = '${{ steps.check.outputs.on_main }}' === 'true'; + const count = parseInt('${{ steps.check.outputs.count }}', 10); + let body; + if (onMain) { + body = [ + '## Challenge 10: PR is from the main branch', + '', + 'This challenge asks you to create a **feature branch**, make a commit locally, ', + 'and push it. Your PR appears to come from main itself.', + '', + 'See [Chapter 14: Git in Practice](https://github.com/Community-Access/git-going-with-github/blob/main/docs/14-git-in-practice.md) for branching instructions.' + ].join('\n'); + } else if (count === 0) { + body = [ + '## Challenge 10: No new commits found', + '', + 'The branch exists but has no commits ahead of main. ', + 'Make sure you committed your changes before pushing.', + ].join('\n'); + } else { + body = [ + '## Challenge 10: Local commit verified!', + '', + 'Found ' + count + ' commit(s) on branch `' + '${{ github.head_ref }}' + '`. ', + 'Your local Git workflow is working. Well done!' + ].join('\n'); + } + try { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body + }); + } catch (error) { + console.error('Could not post Challenge 10 result:', error.message); + } diff --git a/learning-room/.github/workflows/autograder-template.yml b/learning-room/.github/workflows/autograder-template.yml index e69de29..57a490c 100644 --- a/learning-room/.github/workflows/autograder-template.yml +++ b/learning-room/.github/workflows/autograder-template.yml @@ -0,0 +1,104 @@ +name: "Challenge 14: Issue Template Validation" +# Validates that the student created a custom YAML issue template + +on: + pull_request: + types: [opened, synchronize, reopened] + paths: + - ".github/ISSUE_TEMPLATE/**" + +permissions: + contents: read + pull-requests: write + +jobs: + check-template: + name: Verify Custom Issue Template + runs-on: ubuntu-latest + + steps: + - name: Checkout PR branch + uses: actions/checkout@v4 + + - name: Find and validate template + id: check + run: | + # Look for student-created templates (not our pre-existing ones) + TEMPLATES=$(find .github/ISSUE_TEMPLATE -name '*.yml' \ + ! -name 'challenge-*.yml' \ + ! -name 'bonus-*.yml' \ + ! -name 'config.yml' 2>/dev/null || true) + + if [ -z "$TEMPLATES" ]; then + echo "found=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + + echo "found=true" >> "$GITHUB_OUTPUT" + + # Check the first custom template for a name field. + # Normalize a UTF-8 BOM because Windows tooling may add one. + FIRST=$(echo "$TEMPLATES" | head -1) + echo "file=$FIRST" >> "$GITHUB_OUTPUT" + CHECK_FILE="/tmp/custom-issue-template.yml" + sed '1s/^\xEF\xBB\xBF//' "$FIRST" > "$CHECK_FILE" + + if grep -q '^name:' "$CHECK_FILE"; then + echo "has_name=true" >> "$GITHUB_OUTPUT" + else + echo "has_name=false" >> "$GITHUB_OUTPUT" + fi + + if grep -q '^description:' "$CHECK_FILE"; then + echo "has_desc=true" >> "$GITHUB_OUTPUT" + else + echo "has_desc=false" >> "$GITHUB_OUTPUT" + fi + + - name: Post result + if: always() + uses: actions/github-script@v7 + with: + script: | + const found = '${{ steps.check.outputs.found }}' === 'true'; + const hasName = '${{ steps.check.outputs.has_name }}' === 'true'; + const hasDesc = '${{ steps.check.outputs.has_desc }}' === 'true'; + const file = '${{ steps.check.outputs.file }}'; + let body; + if (!found) { + body = [ + '## Challenge 14: No custom template found', + '', + 'Create a new `.yml` file in `.github/ISSUE_TEMPLATE/` with at least ', + 'a `name:` and `description:` field.', + '', + 'See [Chapter 17: Issue Templates](https://github.com/Community-Access/git-going-with-github/blob/main/docs/17-issue-templates.md) for the YAML format.' + ].join('\n'); + } else if (!hasName || !hasDesc) { + const missing = []; + if (!hasName) missing.push('`name:`'); + if (!hasDesc) missing.push('`description:`'); + body = [ + '## Challenge 14: Template found but missing required fields', + '', + 'Found `' + file + '` but it is missing: ' + missing.join(', ') + '.', + '', + 'Add the missing fields and push again.' + ].join('\n'); + } else { + body = [ + '## Challenge 14: Custom issue template looks great!', + '', + 'Found `' + file + '` with valid `name` and `description` fields. Well done!' + ].join('\n'); + } + try { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body + }); + } catch (error) { + console.error('Could not post Challenge 14 result:', error.message); + } diff --git a/learning-room/.github/workflows/autograder-watchdog.yml b/learning-room/.github/workflows/autograder-watchdog.yml deleted file mode 100644 index e69de29..0000000