Make legacy mcf cleanup optional in tutorial #50
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Deploy Jekyll with GitHub Pages dependencies preinstalled | |
| on: | |
| push: | |
| branches: ["main"] | |
| paths-ignore: | |
| - 'VERSION' | |
| workflow_dispatch: | |
| permissions: | |
| contents: write | |
| pages: write | |
| id-token: write | |
| concurrency: | |
| group: "pages" | |
| cancel-in-progress: false | |
| jobs: | |
| build: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| version: ${{ steps.version.outputs.VERSION }} | |
| release: ${{ steps.changes.outputs.release }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Check for content changes | |
| id: changes | |
| run: | | |
| if [ "${{ github.event_name }}" = "push" ] && [ -n "${{ github.event.before }}" ] && [ "${{ github.event.before }}" != "0000000000000000000000000000000000000000" ]; then | |
| CONTENT_CHANGES=$(git diff --name-only "${{ github.event.before }}" "${{ github.sha }}" 2>/dev/null | grep -E '^(README\.md|TUTORIAL\.md|CREDITS\.md|docs/|skills/|\.github/workflows/jekyll-gh-pages\.yml)' || true) | |
| else | |
| CONTENT_CHANGES=$(git ls-files README.md TUTORIAL.md CREDITS.md docs skills .github/workflows/jekyll-gh-pages.yml 2>/dev/null || true) | |
| fi | |
| if [ -n "$CONTENT_CHANGES" ]; then | |
| echo "Documentation or packaging changes detected:" | |
| echo "$CONTENT_CHANGES" | |
| echo "release=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "No documentation changes, skipping release" | |
| echo "release=false" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Validate skill reference links | |
| run: | | |
| python3 - <<'PY' | |
| from pathlib import Path | |
| import re | |
| repo = Path(".").resolve() | |
| link_re = re.compile(r"\[[^\]]+\]\(([^)#]+)(?:#[^)]+)?\)") | |
| ignore = {"overview-template.md"} | |
| failures = [] | |
| for path in sorted((repo / "skills").glob("*/references/*.md")): | |
| if path.name.endswith("-template.md") or path.name in ignore: | |
| continue | |
| text = path.read_text(encoding="utf-8") | |
| for target in link_re.findall(text): | |
| if target.startswith(("http://", "https://", "mailto:", "/", "#")): | |
| continue | |
| resolved = (path.parent / target).resolve() | |
| try: | |
| rel = resolved.relative_to(repo) | |
| except ValueError: | |
| continue | |
| if not (repo / rel).exists(): | |
| failures.append(f"{path.relative_to(repo)} -> {target}") | |
| if failures: | |
| print("Broken local reference links found:") | |
| for failure in failures: | |
| print(f" - {failure}") | |
| raise SystemExit(1) | |
| print("Skill reference links OK") | |
| PY | |
| - name: Set version | |
| id: version | |
| run: | | |
| BASE_VERSION="$(tr -d '\r\n' < VERSION 2>/dev/null || true)" | |
| if [[ ! "$BASE_VERSION" =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then | |
| echo "ERROR: VERSION must be in SemVer format: MAJOR.MINOR.PATCH (example: 1.2.0). Got: '$BASE_VERSION'" >&2 | |
| exit 2 | |
| fi | |
| MAJOR="${BASH_REMATCH[1]}" | |
| MINOR="${BASH_REMATCH[2]}" | |
| VERSION="${MAJOR}.${MINOR}.${{ github.run_number }}" | |
| echo "VERSION=$VERSION" >> $GITHUB_OUTPUT | |
| echo "$VERSION" > VERSION | |
| - name: Commit version file | |
| if: steps.changes.outputs.release == 'true' | |
| run: | | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| git add VERSION | |
| git diff --staged --quiet || git commit -m "Bump version to ${{ steps.version.outputs.VERSION }}" | |
| git push | |
| - name: Add version and build date to config | |
| run: | | |
| echo "version: \"${{ steps.version.outputs.VERSION }}\"" >> github-pages/_config.yml | |
| echo "build_date: \"$(date +'%Y-%m-%d')\"" >> github-pages/_config.yml | |
| - name: Generate catalog pages | |
| run: | | |
| SITE_URL="$(awk -F'\"' '/^url:/{print $2}' github-pages/_config.yml)" | |
| export SITE_URL | |
| python3 - <<'PY' | |
| from __future__ import annotations | |
| import os | |
| from pathlib import Path | |
| repo = Path(".") | |
| site_url = os.environ["SITE_URL"].rstrip("/") | |
| github_raw = "https://raw.githubusercontent.com/managedcode/MCAF/main" | |
| github_blob = "https://github.com/managedcode/MCAF/blob/main" | |
| def parse_frontmatter(skill_md: Path) -> dict[str, str]: | |
| text = skill_md.read_text(encoding="utf-8") | |
| lines = text.splitlines() | |
| data: dict[str, str] = {} | |
| if not lines or lines[0].strip() != "---": | |
| return data | |
| for line in lines[1:]: | |
| if line.strip() == "---": | |
| break | |
| if ":" not in line: | |
| continue | |
| key, value = line.split(":", 1) | |
| data[key.strip()] = value.strip().strip("\"'") | |
| return data | |
| def replace_marker_block( | |
| text: str, | |
| begin_marker: str, | |
| end_marker: str, | |
| replacement_lines: list[str], | |
| ) -> str: | |
| begin_idx = text.find(begin_marker) | |
| if begin_idx == -1: | |
| raise SystemExit(f"Missing marker: {begin_marker}") | |
| begin_line_end = text.find("\n", begin_idx) | |
| if begin_line_end == -1: | |
| raise SystemExit(f"Broken marker line: {begin_marker}") | |
| end_idx = text.find(end_marker, begin_line_end + 1) | |
| if end_idx == -1: | |
| raise SystemExit(f"Missing marker: {end_marker}") | |
| replacement = "\n".join(replacement_lines).rstrip() | |
| return text[: begin_line_end + 1] + replacement + "\n" + text[end_idx:] | |
| skills: list[dict[str, str]] = [] | |
| for skill_md in sorted((repo / "skills").glob("*/SKILL.md")): | |
| skill_dir = skill_md.parent | |
| skill_name = skill_dir.name | |
| metadata = parse_frontmatter(skill_md) | |
| skills.append( | |
| { | |
| "name": skill_name, | |
| "description": metadata.get("description", ""), | |
| "folder_url": f"{github_blob}/skills/{skill_name}", | |
| "skill_md_url": f"{github_raw}/skills/{skill_name}/SKILL.md", | |
| } | |
| ) | |
| templates_page = repo / "github-pages" / "templates.md" | |
| templates_page.write_text( | |
| "\n".join( | |
| [ | |
| "---", | |
| "layout: default", | |
| "title: Templates", | |
| "description: Minimal MCAF bootstrap templates for repository setup. Download AGENTS.md and CLAUDE.md directly, then fetch skills from their GitHub folders.", | |
| "keywords: MCAF templates, AGENTS.md template, CLAUDE.md template, AI coding bootstrap, MCAF bootstrap", | |
| "nav_order: 2", | |
| "---", | |
| "", | |
| "# Templates", | |
| "", | |
| "Public bootstrap templates are intentionally minimal. Everything else is distributed through skills.", | |
| "", | |
| "<div class=\"templates-list\">", | |
| "<div class=\"template-item\">", | |
| "<span class=\"template-name\">AGENTS</span>", | |
| "<div class=\"template-links\">", | |
| f"<a href=\"{github_blob}/docs/templates/AGENTS.md\">View</a>", | |
| f"<a href=\"{github_raw}/docs/templates/AGENTS.md\">Download</a>", | |
| "</div>", | |
| "</div>", | |
| "<div class=\"template-item\">", | |
| "<span class=\"template-name\">CLAUDE</span>", | |
| "<div class=\"template-links\">", | |
| f"<a href=\"{github_blob}/docs/templates/CLAUDE.md\">View</a>", | |
| f"<a href=\"{github_raw}/docs/templates/CLAUDE.md\">Download</a>", | |
| "</div>", | |
| "</div>", | |
| "</div>", | |
| "", | |
| ] | |
| ) | |
| + "\n", | |
| encoding="utf-8", | |
| ) | |
| skills_page_lines = [ | |
| "---", | |
| "layout: default", | |
| "title: Skills", | |
| "description: Baseline MCAF skills for architecture, ADRs, feature specs, maintainability, testing, CI/CD, security, observability, UI/UX, ML/AI delivery, and governance.", | |
| "keywords: MCAF skills, Agent Skills, SKILL.md, Codex skills, Claude Code skills, AI engineering workflows", | |
| "nav_order: 5", | |
| "---", | |
| "", | |
| "# Skills", | |
| "", | |
| f"Use these skill folders with the [tutorial]({site_url}/tutorial.html).", | |
| "", | |
| "<div class=\"templates-list\">", | |
| ] | |
| for skill in skills: | |
| skills_page_lines.extend( | |
| [ | |
| "<div class=\"template-item\">", | |
| f"<span class=\"template-name\">{skill['name']}</span>", | |
| f"<p>{skill['description']}</p>", | |
| "<div class=\"template-links\">", | |
| f"<a href=\"{skill['folder_url']}\">Folder</a>", | |
| f"<a href=\"{skill['skill_md_url']}\">Raw SKILL</a>", | |
| f"<a href=\"{github_blob}/skills/{skill['name']}/SKILL.md\">View</a>", | |
| "</div>", | |
| "</div>", | |
| ] | |
| ) | |
| skills_page_lines.extend(["</div>", ""]) | |
| (repo / "github-pages" / "skills.md").write_text( | |
| "\n".join(skills_page_lines) + "\n", | |
| encoding="utf-8", | |
| ) | |
| dotnet_optional_exclude = { | |
| "mcaf-dotnet-features", | |
| "mcaf-dotnet-quality-ci", | |
| "mcaf-dotnet-complexity", | |
| "mcaf-dotnet-xunit", | |
| "mcaf-dotnet-tunit", | |
| "mcaf-dotnet-mstest", | |
| } | |
| dotnet_optional = sorted( | |
| skill["name"] | |
| for skill in skills | |
| if skill["name"].startswith("mcaf-dotnet-") | |
| and skill["name"] not in dotnet_optional_exclude | |
| ) | |
| dotnet_optional_lines = ( | |
| [f"- `{skill_name}`" for skill_name in dotnet_optional] | |
| if dotnet_optional | |
| else ["- _No optional .NET skills found._"] | |
| ) | |
| all_skills_lines = [ | |
| ( | |
| f"- `{skill['name']}` — " | |
| f"[Folder]({skill['folder_url']}), " | |
| f"[Raw SKILL]({skill['skill_md_url']})" | |
| ) | |
| for skill in skills | |
| ] | |
| tutorial_body = (repo / "TUTORIAL.md").read_text(encoding="utf-8") | |
| tutorial_body = replace_marker_block( | |
| tutorial_body, | |
| "<!-- MCAF:DOTNET-OPTIONAL-SKILLS-BEGIN -->", | |
| "<!-- MCAF:DOTNET-OPTIONAL-SKILLS-END -->", | |
| dotnet_optional_lines, | |
| ) | |
| tutorial_body = replace_marker_block( | |
| tutorial_body, | |
| "<!-- MCAF:ALL-SKILLS-BEGIN -->", | |
| "<!-- MCAF:ALL-SKILLS-END -->", | |
| all_skills_lines, | |
| ) | |
| tutorial_frontmatter = "\n".join( | |
| [ | |
| "---", | |
| "layout: default", | |
| "title: Tutorial", | |
| "description: Step-by-step tutorial for bootstrapping an MCAF v1.2 repository through URLs, bootstrap templates, and direct GitHub skill folders.", | |
| "keywords: MCAF tutorial, AGENTS.md setup, skill-first install, AI coding bootstrap, Codex skills, Claude Code skills", | |
| "nav_order: 3", | |
| "---", | |
| "", | |
| ] | |
| ) | |
| (repo / "github-pages" / "tutorial.md").write_text( | |
| tutorial_frontmatter + tutorial_body.rstrip() + "\n", | |
| encoding="utf-8", | |
| ) | |
| PY | |
| - name: Copy CREDITS to github-pages | |
| run: | | |
| cat > github-pages/credits.md << 'EOF' | |
| --- | |
| layout: default | |
| title: Credits | |
| description: Credits and acknowledgments for the people and projects that shaped the Managed Code Coding AI Framework. | |
| keywords: MCAF credits, Managed Code, AI framework contributors, engineering playbook acknowledgments | |
| nav_order: 4 | |
| --- | |
| EOF | |
| sed -i 's/^ //' github-pages/credits.md | |
| cat CREDITS.md >> github-pages/credits.md | |
| - name: Copy README to github-pages with TOC | |
| run: | | |
| WORDS=$(wc -w < README.md) | |
| MINUTES=$(( (WORDS + 200) / 200 )) | |
| cat > github-pages/index.md << 'EOF' | |
| --- | |
| layout: default | |
| title: Concepts | |
| description: MCAF is a skill-first framework for building real software with AI coding agents using AGENTS.md, repository-native skills, and automated verification. | |
| keywords: MCAF, Coding AI Framework, AGENTS.md, AI-assisted development, AI coding skills, real software, Codex, Claude Code | |
| is_home: true | |
| nav_order: 1 | |
| --- | |
| EOF | |
| sed -i 's/^ //' github-pages/index.md | |
| awk '/^---$/{exit} {print}' README.md >> github-pages/index.md | |
| echo "" >> github-pages/index.md | |
| echo "<p class=\"reading-time\">${MINUTES} min read</p>" >> github-pages/index.md | |
| echo '' >> github-pages/index.md | |
| echo '<nav class="toc">' >> github-pages/index.md | |
| echo '<div class="toc-title">Table of Contents</div>' >> github-pages/index.md | |
| echo '<ol>' >> github-pages/index.md | |
| echo '<li><a href="#1-what-mcaf-is">What MCAF Is</a></li>' >> github-pages/index.md | |
| echo '<li><a href="#2-context">Context</a></li>' >> github-pages/index.md | |
| echo '<li><a href="#3-verification">Verification</a></li>' >> github-pages/index.md | |
| echo '<li><a href="#4-instructions-and-agentsmd">Instructions and AGENTS.md</a></li>' >> github-pages/index.md | |
| echo '<li><a href="#5-coding-and-testability">Coding and Testability</a></li>' >> github-pages/index.md | |
| echo '<li><a href="#6-perspectives">Perspectives</a></li>' >> github-pages/index.md | |
| echo '<li><a href="#7-development-cycle">Development Cycle</a></li>' >> github-pages/index.md | |
| echo '<li><a href="#8-ai-participation-modes">AI Participation Modes</a></li>' >> github-pages/index.md | |
| echo '<li><a href="#9-adopting-mcaf-in-a-repository">Adopting MCAF</a></li>' >> github-pages/index.md | |
| echo '</ol>' >> github-pages/index.md | |
| echo '</nav>' >> github-pages/index.md | |
| echo '' >> github-pages/index.md | |
| echo '---' >> github-pages/index.md | |
| awk '/^---$/{found=1; next} found{print}' README.md >> github-pages/index.md | |
| - name: Setup Pages | |
| uses: actions/configure-pages@v5 | |
| - name: Build with Jekyll | |
| uses: actions/jekyll-build-pages@v1 | |
| with: | |
| source: ./github-pages | |
| destination: ./_site | |
| - name: Upload artifact | |
| uses: actions/upload-pages-artifact@v3 | |
| with: | |
| path: ./_site | |
| deploy: | |
| runs-on: ubuntu-latest | |
| needs: build | |
| steps: | |
| - name: Deploy to GitHub Pages | |
| id: deployment | |
| uses: actions/deploy-pages@v4 | |
| release: | |
| runs-on: ubuntu-latest | |
| needs: build | |
| if: needs.build.outputs.release == 'true' | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Create Release | |
| uses: softprops/action-gh-release@v1 | |
| with: | |
| tag_name: v${{ needs.build.outputs.version }} | |
| name: Release ${{ needs.build.outputs.version }} | |
| body: | | |
| ## MCAF Release ${{ needs.build.outputs.version }} | |
| Managed Code Coding AI Framework | |
| draft: false | |
| prerelease: false | |
| generate_release_notes: true |