fix(board): track empty todo/ with .gitkeep like doing/ and done/ #9
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: GraphStack CI | |
| on: | |
| push: | |
| branches: [main, dev, master] | |
| pull_request: | |
| branches: [main, master] | |
| jobs: | |
| # ────────────────────────────────────────────────────────────────── | |
| # Job 1: Bash syntax check (Linux only — bash isn't on Windows native) | |
| # ────────────────────────────────────────────────────────────────── | |
| bash-syntax: | |
| name: Bash syntax check | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: bash -n install.sh | |
| run: bash -n install.sh | |
| - name: bash -n scripts/board.sh | |
| run: bash -n scripts/board.sh | |
| - name: bash -n scripts/post-commit | |
| run: bash -n scripts/post-commit | |
| # ────────────────────────────────────────────────────────────────── | |
| # Job 2: Cross-platform validation matrix (Win + Mac + Linux) | |
| # ────────────────────────────────────────────────────────────────── | |
| validate: | |
| name: Validate (${{ matrix.os }}) | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ubuntu-latest, macos-latest, windows-latest] | |
| runs-on: ${{ matrix.os }} | |
| steps: | |
| # depth 2+ required: validate --fail-stale-graph matches graph commit to HEAD~1 | |
| # when graph artifacts land in a follow-up commit (v4.1 release pattern). | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 2 | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.11" | |
| - name: Install package, pytest, and Graphify | |
| run: | | |
| python -m pip install --quiet pytest | |
| python -m pip install --quiet -e . | |
| python -m pip install --quiet -r requirements.txt | |
| - name: Verify required files exist (Python) | |
| shell: python | |
| run: | | |
| import sys | |
| from pathlib import Path | |
| required = [ | |
| ".cursor/skills/architect/ARCHITECT.md", | |
| ".cursor/skills/builder/BUILDER.md", | |
| ".cursor/skills/reviewer/REVIEWER.md", | |
| ".cursor/skills/qa/QA.md", | |
| ".cursor/skills/ship/SHIP.md", | |
| ".cursor/skills/bootstrapper/BOOTSTRAPPER.md", | |
| "orchestrator/ORCHESTRATOR.md", | |
| "orchestrator/TOKEN_OPTIMIZER.md", | |
| ".cursor/rules/graphstack.mdc", | |
| ".cursor/commands/graphstack.md", | |
| "handoff/BRIEF.md", | |
| "handoff/REVIEW.md", | |
| "handoff/BOOTSTRAP.md", | |
| "handoff/STATE.md", | |
| "handoff/board/README.md", | |
| "handoff/board/todo/.gitkeep", | |
| "handoff/board/doing/.gitkeep", | |
| "handoff/board/done/.gitkeep", | |
| "docs/CURSOR_PROMPTS.md", | |
| "install.sh", | |
| "install.ps1", | |
| "requirements.txt", | |
| "pyproject.toml", | |
| "scripts/board.sh", | |
| "scripts/board.ps1", | |
| "scripts/post-commit", | |
| "scripts/post-commit.ps1", | |
| "scripts/graphstack/__init__.py", | |
| "scripts/graphstack/__main__.py", | |
| "scripts/graphstack/cli.py", | |
| "scripts/graphstack/board.py", | |
| "scripts/graphstack/installer.py", | |
| "scripts/graphstack/hook.py", | |
| "scripts/graphstack/platform_utils.py", | |
| "scripts/graphstack/constants.py", | |
| "scripts/graphstack/validate.py", | |
| ".graphifyignore", | |
| "docs/case-studies/graphstack-self.md", | |
| ] | |
| missing = [p for p in required if not Path(p).is_file()] | |
| for p in required: | |
| mark = "OK" if Path(p).is_file() else "MISSING" | |
| print(f"[{mark}] {p}") | |
| if missing: | |
| print(f"\n{len(missing)} file(s) missing.", file=sys.stderr) | |
| sys.exit(1) | |
| - name: Validate GNAP task schema (board new) | |
| shell: python | |
| run: | | |
| import json, subprocess, sys | |
| from pathlib import Path | |
| subprocess.run( | |
| [sys.executable, "-m", "graphstack", "board", "new", "schema-check", "CI schema probe"], | |
| check=True, | |
| ) | |
| path = Path("handoff/board/todo/schema-check.json") | |
| data = json.loads(path.read_text(encoding="utf-8")) | |
| required = ["id", "title", "status", "created_at"] | |
| missing = [k for k in required if k not in data] | |
| path.unlink(missing_ok=True) | |
| if missing: | |
| print(f"board new() missing keys: {missing}", file=sys.stderr) | |
| sys.exit(1) | |
| print("GNAP task schema OK") | |
| - name: Run graphstack pytest suite | |
| run: python -m pytest scripts/graphstack/tests -v | |
| - name: Validate project layout (graphstack validate) | |
| run: python -m graphstack validate | |
| - name: Refresh knowledge graph (CI) | |
| run: graphify update . | |
| - name: Fail if knowledge graph is stale vs HEAD | |
| run: python -m graphstack validate --fail-stale-graph | |
| - name: Board smoke test (Python module — cross-platform) | |
| shell: python | |
| run: | | |
| import os, subprocess, sys | |
| from pathlib import Path | |
| def run(*args): | |
| proc = subprocess.run( | |
| [sys.executable, "-m", "graphstack", "board", *args], | |
| check=True, | |
| ) | |
| return proc | |
| run("status") | |
| run("new", "py-ci-task", "Pythonic", "CI", "task") | |
| run("claim", "py-ci-task", "reviewer") | |
| run("complete", "py-ci-task") | |
| done = Path("handoff/board/done/py-ci-task.json") | |
| if done.exists(): | |
| done.unlink() | |
| print("Python board lifecycle OK") | |
| - name: Board smoke test (bash shim — Unix only) | |
| if: runner.os != 'Windows' | |
| run: | | |
| bash scripts/board.sh status | |
| bash scripts/board.sh new sh-ci-task Bash smoke test task | |
| bash scripts/board.sh claim sh-ci-task builder | |
| bash scripts/board.sh complete sh-ci-task | |
| rm -f handoff/board/done/sh-ci-task.json | |
| echo "bash shim board lifecycle OK" | |
| - name: Board smoke test (PowerShell shim — Windows only) | |
| if: runner.os == 'Windows' | |
| shell: pwsh | |
| run: | | |
| .\scripts\board.ps1 status | |
| .\scripts\board.ps1 new ps-ci-task PowerShell smoke test task | |
| .\scripts\board.ps1 claim ps-ci-task builder | |
| .\scripts\board.ps1 complete ps-ci-task | |
| if (Test-Path handoff/board/done/ps-ci-task.json) { | |
| Remove-Item handoff/board/done/ps-ci-task.json | |
| } | |
| Write-Host "PowerShell shim board lifecycle OK" | |
| # ────────────────────────────────────────────────────────────────── | |
| # Job 3: Markdown size + link sanity check (Linux only — runs once) | |
| # ────────────────────────────────────────────────────────────────── | |
| markdown-checks: | |
| name: Markdown lint | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Detect short markdown files | |
| shell: python | |
| run: | | |
| import sys | |
| from pathlib import Path | |
| warnings = 0 | |
| for path in sorted(Path(".").glob(".cursor/skills/**/*.md")) + sorted(Path("orchestrator").glob("*.md")): | |
| lines = sum(1 for _ in path.open(encoding="utf-8")) | |
| if lines < 10: | |
| print(f"WARN: {path} seems too short ({lines} lines)") | |
| warnings += 1 | |
| if warnings: | |
| print(f"\n{warnings} short markdown file(s) detected (warning only).") | |
| else: | |
| print("All markdown files pass size threshold.") | |
| - name: Check internal links resolve | |
| shell: python | |
| run: | | |
| import re, sys | |
| from pathlib import Path | |
| link_re = re.compile(r"\]\(([^)#]+?)(?:#[^)]*)?\)") | |
| broken = [] | |
| for md in Path(".").rglob("*.md"): | |
| if any(part in md.parts for part in (".git", "node_modules", "graphify-out")): | |
| continue | |
| text = md.read_text(encoding="utf-8", errors="replace") | |
| for match in link_re.finditer(text): | |
| target = match.group(1).strip() | |
| if target.startswith(("http://", "https://", "mailto:")): | |
| continue | |
| if target.startswith("#") or "://" in target: | |
| continue | |
| resolved = (md.parent / target).resolve() | |
| if not resolved.exists(): | |
| broken.append((md, target)) | |
| for md, target in broken: | |
| print(f"BROKEN: {md} -> {target}") | |
| if broken: | |
| print(f"\n{len(broken)} broken link(s) found.", file=sys.stderr) | |
| sys.exit(1) | |
| print("All relative markdown links resolve.") |