Skip to content

Make legacy mcf cleanup optional in tutorial #50

Make legacy mcf cleanup optional in tutorial

Make legacy mcf cleanup optional in tutorial #50

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