From f58317d0790949ab5003529b5cef7664720556e5 Mon Sep 17 00:00:00 2001 From: Jason Walker <913443@dadeschools.net> Date: Mon, 6 Jul 2026 01:36:10 -0400 Subject: [PATCH] Add docs/wiki source tree and Gitea Wiki sync helper (#224) Bootstrap repo-tracked wiki pages (10 required pages), the operator- confirmed sync script, PR template publication gate, and safety tests so Gitea-Tools can publish to the live Gitea Wiki per issue #224. Co-Authored-By: Claude Opus 4.8 (1M context) --- .gitea/pull_request_template.md | 20 ++++++ docs/wiki/History.md | 15 ++++ docs/wiki/Home.md | 35 +++++++++ docs/wiki/Identity-and-Profiles.md | 39 ++++++++++ docs/wiki/MCP-Tools.md | 34 +++++++++ docs/wiki/Open-Decisions.md | 11 +++ docs/wiki/Operator-Guide.md | 31 ++++++++ docs/wiki/Repositories.md | 37 ++++++++++ docs/wiki/Runbooks.md | 40 +++++++++++ docs/wiki/Safety-and-Gates.md | 19 +++++ docs/wiki/Workflow.md | 28 ++++++++ scripts/sync-gitea-wiki.sh | 86 ++++++++++++++++++++++ tests/test_wiki_sync.py | 112 +++++++++++++++++++++++++++++ 13 files changed, 507 insertions(+) create mode 100644 .gitea/pull_request_template.md create mode 100644 docs/wiki/History.md create mode 100644 docs/wiki/Home.md create mode 100644 docs/wiki/Identity-and-Profiles.md create mode 100644 docs/wiki/MCP-Tools.md create mode 100644 docs/wiki/Open-Decisions.md create mode 100644 docs/wiki/Operator-Guide.md create mode 100644 docs/wiki/Repositories.md create mode 100644 docs/wiki/Runbooks.md create mode 100644 docs/wiki/Safety-and-Gates.md create mode 100644 docs/wiki/Workflow.md create mode 100755 scripts/sync-gitea-wiki.sh create mode 100644 tests/test_wiki_sync.py diff --git a/.gitea/pull_request_template.md b/.gitea/pull_request_template.md new file mode 100644 index 0000000..3febc9c --- /dev/null +++ b/.gitea/pull_request_template.md @@ -0,0 +1,20 @@ +## Description + +[Summary of changes and issue number closed.] + +Closes #[Issue Number] + +## Checklist + +- [ ] I have verified my identity matches the required role. +- [ ] No secrets, tokens, keychain IDs, or raw service URLs are committed. +- [ ] All tests pass for touched code. +- [ ] `git diff --check` is clean. + +## Documentation and Wiki + +- [ ] Does this change require a wiki update (workflows, tools, profiles, runbooks)? +- [ ] If yes, has `docs/wiki/` been updated accordingly? +- [ ] If wiki pages changed, plan the Gitea Wiki sync after merge (`scripts/sync-gitea-wiki.sh`, see Runbooks). +- [ ] Readiness gate (#224): the Gitea Wiki is populated and current for this repo — verify the repo **Wiki tab**, not `docs/wiki/`. If stale or empty, record the required sync as a follow-up before approval. +- [ ] If this PR closes a wiki-related issue: closure requires live Gitea Wiki proof links (Wiki Home plus page listing or wiki git log). Markdown in `docs/wiki/`, sync-helper code, or policy docs alone are not sufficient to close a wiki issue. \ No newline at end of file diff --git a/docs/wiki/History.md b/docs/wiki/History.md new file mode 100644 index 0000000..520e6f0 --- /dev/null +++ b/docs/wiki/History.md @@ -0,0 +1,15 @@ +# Project History + +## Navigation + +- [Home](Home.md) | [Operator Guide](Operator-Guide.md) | [Repositories Map](Repositories.md) | [Identity and Profiles](Identity-and-Profiles.md) | [Workflow Model](Workflow.md) | [Safety and Gates](Safety-and-Gates.md) | [MCP Tools Reference](MCP-Tools.md) | [Operator Runbooks](Runbooks.md) | [Open Decisions](Open-Decisions.md) | [Project History](History.md) + +## 2026-07-06 + +- **Wiki bootstrap (#224)** — Added repo-tracked `docs/wiki/` (10 pages), `scripts/sync-gitea-wiki.sh`, PR template gate, and sync safety tests for Gitea-Tools publication. + +## Prior milestones + +- Gated review/merge path (#16), task capability resolver (#69), issue-write tool gates. +- Canonical JSON execution profiles (#19) and thin MCP launchers. +- Role session router (#206) and review decision lock (#211). \ No newline at end of file diff --git a/docs/wiki/Home.md b/docs/wiki/Home.md new file mode 100644 index 0000000..24c2cd5 --- /dev/null +++ b/docs/wiki/Home.md @@ -0,0 +1,35 @@ +# Gitea-Tools Project Wiki + +Welcome to the version-controlled wiki for the Gitea-Tools MCP server and CLI tooling. + +## Navigation + +- [Home](Home.md) +- [Operator Guide](Operator-Guide.md) +- [Repositories Map](Repositories.md) +- [Identity and Profiles](Identity-and-Profiles.md) +- [Workflow Model](Workflow.md) +- [Safety and Gates](Safety-and-Gates.md) +- [MCP Tools Reference](MCP-Tools.md) +- [Operator Runbooks](Runbooks.md) +- [Open Decisions](Open-Decisions.md) +- [Project History](History.md) + +## Gitea Wiki mirror + +These repo-tracked pages are the **source of truth**. The Gitea native Wiki +for this repository is a read-only convenience mirror generated from this +directory with `scripts/sync-gitea-wiki.sh` (dry-run by default; see +[Runbooks](Runbooks.md)). Never edit the Gitea Wiki directly — change the +pages here through a PR, then sync. + +## Project overview + +Gitea-Tools provides the Gitea MCP server, execution-profile configuration, +identity handling, and CLI helpers used across MCP Control Plane workflows. +It enforces author/reviewer separation, gated review/merge, task-capability +resolution, and fail-closed safety rails in code. + +Canonical long-form docs also live under `docs/` in the repository (for example +`docs/gitea-execution-profiles.md` and `docs/llm-workflow-runbooks.md`). The +wiki summarizes operator-facing rules; the repo docs carry implementation detail. \ No newline at end of file diff --git a/docs/wiki/Identity-and-Profiles.md b/docs/wiki/Identity-and-Profiles.md new file mode 100644 index 0000000..763a3bb --- /dev/null +++ b/docs/wiki/Identity-and-Profiles.md @@ -0,0 +1,39 @@ +# Identity and Profiles + +## Navigation + +- [Home](Home.md) | [Operator Guide](Operator-Guide.md) | [Repositories Map](Repositories.md) | [Identity and Profiles](Identity-and-Profiles.md) | [Workflow Model](Workflow.md) | [Safety and Gates](Safety-and-Gates.md) | [MCP Tools Reference](MCP-Tools.md) | [Operator Runbooks](Runbooks.md) | [Open Decisions](Open-Decisions.md) | [Project History](History.md) + +The LLM is not the role — the **MCP execution profile** is the role. Profiles +bind an authenticated Gitea identity to an allowed operation set. + +## Reference profiles (prgs) + +### Author / implementer + +- **Profile:** `prgs-author` +- **Typical identity:** `jcwalker3` +- **Allowed:** branch create/push, PR create, issue comment/create/close, repo commit, read. +- **Forbidden:** PR approve, merge, request_changes. + +### Reviewer / merger + +- **Profile:** `prgs-reviewer` +- **Typical identity:** `sysadmin` +- **Allowed:** PR review/approve/merge/request_changes, issue comment, read. +- **Forbidden:** branch push, PR create, repo commit. + +## Configuration + +Profiles are defined in the canonical JSON config (`GITEA_MCP_CONFIG`, typically +`~/.config/gitea-tools/profiles.json`). Launchers are thin: they set +`GITEA_MCP_PROFILE` and point at the config file. Credentials resolve from +keychain or env references — never inline in client configs. + +See `docs/gitea-execution-profiles.md` in the repository for the full model. + +## Profile switching + +Use separate MCP server namespaces (`gitea-tools` author vs `gitea-reviewer`) +or distinct launcher entries. Runtime in-place profile switching is disabled by +default (fail closed). \ No newline at end of file diff --git a/docs/wiki/MCP-Tools.md b/docs/wiki/MCP-Tools.md new file mode 100644 index 0000000..38af50e --- /dev/null +++ b/docs/wiki/MCP-Tools.md @@ -0,0 +1,34 @@ +# MCP Tools Reference + +## Navigation + +- [Home](Home.md) | [Operator Guide](Operator-Guide.md) | [Repositories Map](Repositories.md) | [Identity and Profiles](Identity-and-Profiles.md) | [Workflow Model](Workflow.md) | [Safety and Gates](Safety-and-Gates.md) | [MCP Tools Reference](MCP-Tools.md) | [Operator Runbooks](Runbooks.md) | [Open Decisions](Open-Decisions.md) | [Project History](History.md) + +## Identity and capability + +- `gitea_whoami` — authenticated user and active profile metadata. +- `gitea_get_profile` / `gitea_get_runtime_context` — allowed and forbidden operations. +- `gitea_resolve_task_capability` — required pre-flight for gated mutations. +- `gitea_route_task_session` — role/session router before task execution. + +## Author tools + +- `gitea_create_issue`, `gitea_create_issue_comment`, `gitea_close_issue` +- `gitea_mark_issue`, `gitea_set_issue_labels`, `gitea_lock_issue` +- `gitea_create_pr`, `gitea_edit_pr`, `gitea_commit_files` +- `gitea_delete_branch` + +## Reviewer tools + +- `gitea_check_pr_eligibility` — read-only eligibility check. +- `gitea_dry_run_pr_review` — validation-phase review mechanics. +- `gitea_mark_final_review_decision` — mark validation complete. +- `gitea_submit_pr_review` / `gitea_review_pr` — gated live review. +- `gitea_merge_pr` — gated merge (only merge path). + +## Read tools + +- `gitea_list_prs`, `gitea_view_pr`, `gitea_list_issues`, `gitea_view_issue` +- `gitea_get_file`, `gitea_list_labels`, `gitea_mirror_refs` + +See the repository `README.md` for the full tool table and client setup. \ No newline at end of file diff --git a/docs/wiki/Open-Decisions.md b/docs/wiki/Open-Decisions.md new file mode 100644 index 0000000..a465ff7 --- /dev/null +++ b/docs/wiki/Open-Decisions.md @@ -0,0 +1,11 @@ +# Open Decisions + +## Navigation + +- [Home](Home.md) | [Operator Guide](Operator-Guide.md) | [Repositories Map](Repositories.md) | [Identity and Profiles](Identity-and-Profiles.md) | [Workflow Model](Workflow.md) | [Safety and Gates](Safety-and-Gates.md) | [MCP Tools Reference](MCP-Tools.md) | [Operator Runbooks](Runbooks.md) | [Open Decisions](Open-Decisions.md) | [Project History](History.md) + +Pending architectural and workflow decisions: + +- **Operation-scoped role selection (#228)** — Move from launcher-profile-dependent routing to per-operation profile resolution. +- **Gitea-Tools wiki for dadeschools remote** — This bootstrap covers `prgs`; dadeschools instance wiki parity is undecided. +- **Server-side self-merge block** — Complement tool gates with Gitea branch protection where available. \ No newline at end of file diff --git a/docs/wiki/Operator-Guide.md b/docs/wiki/Operator-Guide.md new file mode 100644 index 0000000..35095ff --- /dev/null +++ b/docs/wiki/Operator-Guide.md @@ -0,0 +1,31 @@ +# Operator Guide + +## Navigation + +- [Home](Home.md) | [Operator Guide](Operator-Guide.md) | [Repositories Map](Repositories.md) | [Identity and Profiles](Identity-and-Profiles.md) | [Workflow Model](Workflow.md) | [Safety and Gates](Safety-and-Gates.md) | [MCP Tools Reference](MCP-Tools.md) | [Operator Runbooks](Runbooks.md) | [Open Decisions](Open-Decisions.md) | [Project History](History.md) + +Handbook for LLM operators and human developers using the Gitea-Tools MCP server. + +## Key rules + +1. **Verify identity first** — Run `gitea_whoami` and confirm the active profile before any mutation. +2. **Resolve task capability** — Call `gitea_resolve_task_capability` for the intended task before gated tools. +3. **One unit of work per session** — Implement one claimed issue *or* review/merge one PR; do not mix author and reviewer mutations in one session. +4. **No self-review / no self-merge** — The authenticated Gitea user must not approve or merge a PR they authored. +5. **Follow the gates** — Prompts express intent; MCP tools enforce safety. Never bypass gates via prompt instructions. + +## Supported Gitea instances + +| Remote | Host | Default org/repo | +|--------|------|------------------| +| `dadeschools` | `gitea.dadeschools.net` | `Contractor / Timesheet` | +| `prgs` | `gitea.prgs.cc` | `Scaled-Tech-Consulting / Timesheet` | + +Always pass `remote` explicitly on tool calls. The server default is `dadeschools`; forgetting `remote` on a `prgs` task hits the wrong host. + +## New session checklist + +1. `gitea_whoami` — confirm authenticated user and profile. +2. `gitea_get_runtime_context` — allowed/forbidden operations for this session. +3. `gitea_resolve_task_capability` — prove the session may perform the planned task. +4. For reviewer work: dry-run validation (`gitea_dry_run_pr_review`) before live review mutations. \ No newline at end of file diff --git a/docs/wiki/Repositories.md b/docs/wiki/Repositories.md new file mode 100644 index 0000000..88724d0 --- /dev/null +++ b/docs/wiki/Repositories.md @@ -0,0 +1,37 @@ +# Repositories Map + +## Navigation + +- [Home](Home.md) | [Operator Guide](Operator-Guide.md) | [Repositories Map](Repositories.md) | [Identity and Profiles](Identity-and-Profiles.md) | [Workflow Model](Workflow.md) | [Safety and Gates](Safety-and-Gates.md) | [MCP Tools Reference](MCP-Tools.md) | [Operator Runbooks](Runbooks.md) | [Open Decisions](Open-Decisions.md) | [Project History](History.md) + +Active MCP Control Plane repositories (authoritative inventory for wiki publication gate #224 / #87): + +| Repository | Purpose | Wiki required | +|------------|---------|---------------| +| `Scaled-Tech-Consulting/Gitea-Tools` | Gitea MCP server, execution profiles, workflow tooling | Yes — live Gitea Wiki on Wiki tab | +| `Scaled-Tech-Consulting/mcp-control-plane` | Orchestrators, audit trails, multi-service controller workflows | Yes — live Gitea Wiki on Wiki tab | + +Repo-tracked `docs/wiki/` is the source of truth; the Gitea Wiki is a mirror. +Wiki-related issues cannot be closed until the live Wiki is verified — see +[Safety and Gates](Safety-and-Gates.md) and [Runbooks](Runbooks.md). + +### Gitea-Tools + +- **Repository:** `Scaled-Tech-Consulting/Gitea-Tools` +- **Purpose:** Gitea MCP server, profile configuration, CLI scripts, safety gates. +- **Base branch:** `master` + +### mcp-control-plane + +- **Repository:** `Scaled-Tech-Consulting/mcp-control-plane` +- **Purpose:** Controller workflows, Jenkins/GlitchTip integrations, audit orchestration. +- **Base branch:** `master` + +## Wiki publication status (#224 readiness gate) + +| Repository | `docs/wiki/` source | Gitea Wiki published | Proof | +|---|---|---|---| +| `Scaled-Tech-Consulting/Gitea-Tools` | yes (10 pages) | pending publication — operator-confirmed sync required | pending | +| `Scaled-Tech-Consulting/mcp-control-plane` | yes (10 pages) | published (verified 2026-07-06) | [Wiki Home](https://gitea.prgs.cc/Scaled-Tech-Consulting/mcp-control-plane/wiki/Home) | + +Update this table whenever a wiki is published, re-synced, or found stale. \ No newline at end of file diff --git a/docs/wiki/Runbooks.md b/docs/wiki/Runbooks.md new file mode 100644 index 0000000..d42fe19 --- /dev/null +++ b/docs/wiki/Runbooks.md @@ -0,0 +1,40 @@ +# Operator Runbooks + +## Navigation + +- [Home](Home.md) | [Operator Guide](Operator-Guide.md) | [Repositories Map](Repositories.md) | [Identity and Profiles](Identity-and-Profiles.md) | [Workflow Model](Workflow.md) | [Safety and Gates](Safety-and-Gates.md) | [MCP Tools Reference](MCP-Tools.md) | [Operator Runbooks](Runbooks.md) | [Open Decisions](Open-Decisions.md) | [Project History](History.md) + +## PR review and merge + +1. `gitea_resolve_task_capability(task="review_pr")` +2. `gitea_check_pr_eligibility` for review and merge actions. +3. Validate locally: tests, `py_compile`, `git diff --check`. +4. `gitea_mark_final_review_decision` → approve via `gitea_review_pr`. +5. `gitea_merge_pr` with pinned head SHA and `confirmation="MERGE PR "`. + +## Gitea Wiki sync + +The Gitea Wiki mirrors `docs/wiki/` (source of truth). After merging wiki changes: + +1. Preview (no network): + ```bash + scripts/sync-gitea-wiki.sh + ``` +2. Operator-confirmed push: + ```bash + GITEA_WIKI_SYNC_CONFIRM="SYNC WIKI Gitea-Tools" scripts/sync-gitea-wiki.sh --push + ``` +3. If clone fails on first run, bootstrap Home via Gitea API or UI, then re-run. + +The script mirrors only `docs/wiki/*.md`, never deletes wiki pages, and never +prints credentials. + +## Wiki Publication Readiness Gate (#224) + +Actual Gitea Wiki publication is a **required repo readiness gate**. + +1. **Closure prevention** — No wiki issue may close on markdown/helper work alone. + Closing requires live-Wiki proof: Wiki Home link plus page listing or wiki git log. +2. **Reviewer checklist** — PR template requires verifying the repo **Wiki tab**. +3. **Publication authority** — `--push` requires exact `GITEA_WIKI_SYNC_CONFIRM` phrase. +4. **Per-repo status** — Update [Repositories Map](Repositories.md) when published. \ No newline at end of file diff --git a/docs/wiki/Safety-and-Gates.md b/docs/wiki/Safety-and-Gates.md new file mode 100644 index 0000000..25c21fb --- /dev/null +++ b/docs/wiki/Safety-and-Gates.md @@ -0,0 +1,19 @@ +# Safety and Gates + +## Navigation + +- [Home](Home.md) | [Operator Guide](Operator-Guide.md) | [Repositories Map](Repositories.md) | [Identity and Profiles](Identity-and-Profiles.md) | [Workflow Model](Workflow.md) | [Safety and Gates](Safety-and-Gates.md) | [MCP Tools Reference](MCP-Tools.md) | [Operator Runbooks](Runbooks.md) | [Open Decisions](Open-Decisions.md) | [Project History](History.md) + +*Prompts express intent; MCP tools enforce safety.* + +## Primary gates + +1. **Fail closed** — Unknown tasks, missing capability resolution, or profile mismatches block mutations. +2. **Task capability map** — `gitea_resolve_task_capability` must precede gated issue/PR mutations. +3. **Issue lock** — `gitea_lock_issue` required before author implementation on a tracked issue. +4. **Head SHA pinning** — Reviews and merges refuse when the PR head moved. +5. **Explicit merge confirmation** — `gitea_merge_pr` requires `confirmation="MERGE PR "`. +6. **No self-review / self-merge** — Authenticated user must differ from PR author for approve/merge. +7. **Review decision lock** — Live review mutations require validation-phase dry-run and `gitea_mark_final_review_decision`. +8. **Redaction** — Tokens, passwords, and keychain material never appear in tool output. +9. **Wiki publication (#224)** — `docs/wiki/` and the sync helper are prerequisites only. Closing a wiki issue requires live Gitea Wiki proof on the repo Wiki tab. See [Runbooks](Runbooks.md#wiki-publication-readiness-gate-224). \ No newline at end of file diff --git a/docs/wiki/Workflow.md b/docs/wiki/Workflow.md new file mode 100644 index 0000000..7722426 --- /dev/null +++ b/docs/wiki/Workflow.md @@ -0,0 +1,28 @@ +# Workflow Model + +## Navigation + +- [Home](Home.md) | [Operator Guide](Operator-Guide.md) | [Repositories Map](Repositories.md) | [Identity and Profiles](Identity-and-Profiles.md) | [Workflow Model](Workflow.md) | [Safety and Gates](Safety-and-Gates.md) | [MCP Tools Reference](MCP-Tools.md) | [Operator Runbooks](Runbooks.md) | [Open Decisions](Open-Decisions.md) | [Project History](History.md) + +## Step 1: Review queue first (reviewer profile) + +1. `gitea_resolve_task_capability(task="review_pr")` +2. List open PRs; pick the oldest eligible PR (not self-authored, mergeable). +3. Pin head SHA; validate diff scope and run tests locally. +4. `gitea_mark_final_review_decision` → `gitea_review_pr` / `gitea_submit_pr_review` +5. `gitea_merge_pr` only with `confirmation="MERGE PR "` and pinned head SHA. + +## Step 2: Implement issues (author profile) + +1. `gitea_resolve_task_capability` for the author task. +2. `gitea_lock_issue` before implementation mutations. +3. Claim with `gitea_mark_issue` / `status:in-progress` label. +4. Branch, implement, test, push, `gitea_create_pr`. +5. Release claim when done; never self-review the PR. + +## Step 3: Operator follow-ups + +Wiki publication, credential provisioning, and cross-repo mirror operations are +operator-confirmed actions — see [Runbooks](Runbooks.md). + +Full Gitea-specific runbooks: `docs/llm-workflow-runbooks.md` in the repository. \ No newline at end of file diff --git a/scripts/sync-gitea-wiki.sh b/scripts/sync-gitea-wiki.sh new file mode 100755 index 0000000..28353cf --- /dev/null +++ b/scripts/sync-gitea-wiki.sh @@ -0,0 +1,86 @@ +#!/usr/bin/env bash +set -euo pipefail + +# sync-gitea-wiki.sh — mirror the repo-tracked docs/wiki/ pages into the +# Gitea native wiki. docs/wiki/ remains the source of truth; the Gitea Wiki +# is a read convenience mirrored FROM it, never edited directly. +# +# scripts/sync-gitea-wiki.sh dry-run (default): print the plan +# scripts/sync-gitea-wiki.sh --push actually sync, ONLY with the exact +# confirmation below +# +# Safety contract: +# - Dry-run is the default and performs no network or repository-mutating +# operation (only a local read of the origin remote URL). +# - A push requires GITEA_WIKI_SYNC_CONFIRM to equal exactly +# "SYNC WIKI " (repo name derived from the origin remote). +# Anything else refuses before any clone happens. +# - The wiki remote is derived from the local origin remote; nothing is +# hardcoded here and no credential material is read, printed, or stored +# by this script — git's own configured auth is used as-is. +# - Only *.md pages from docs/wiki/ are mirrored; nothing else is touched +# and no page is deleted from the wiki by this script. + +here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +repo_root="$(cd "$here/.." && pwd)" +wiki_src="$repo_root/docs/wiki" + +[ -d "$wiki_src" ] || { echo "error: $wiki_src not found" >&2; exit 1; } + +if git -C "$repo_root" remote get-url origin >/dev/null 2>&1; then + origin_url="$(git -C "$repo_root" remote get-url origin)" +elif git -C "$repo_root" remote get-url prgs >/dev/null 2>&1; then + origin_url="$(git -C "$repo_root" remote get-url prgs)" +else + echo "error: configure an origin or prgs git remote" >&2 + exit 1 +fi +repo_name="$(basename "$origin_url" .git)" +wiki_remote="${origin_url%.git}.wiki.git" +expected_confirm="SYNC WIKI $repo_name" + +pages=() +while IFS= read -r -d '' f; do + pages+=("$(basename "$f")") +done < <(find "$wiki_src" -maxdepth 1 -name '*.md' -print0 | sort -z) + +mode="${1:-}" + +if [ "$mode" != "--push" ]; then + echo "dry-run: would sync ${#pages[@]} pages from docs/wiki/ to the '$repo_name' Gitea Wiki:" + for p in "${pages[@]}"; do + echo " $p" + done + echo "dry-run: no git or network operation performed." + echo "To sync for real: GITEA_WIKI_SYNC_CONFIRM=\"$expected_confirm\" $0 --push" + exit 0 +fi + +if [ "${GITEA_WIKI_SYNC_CONFIRM:-}" != "$expected_confirm" ]; then + echo "refused: --push requires GITEA_WIKI_SYNC_CONFIRM to equal exactly:" >&2 + echo " $expected_confirm" >&2 + exit 1 +fi + +workdir="$(mktemp -d)" +trap 'rm -rf "$workdir"' EXIT + +echo "cloning wiki repository for '$repo_name'" +if ! git clone --quiet -- "$wiki_remote" "$workdir/wiki" 2>/dev/null; then + echo "error: could not clone the wiki repository. If this repo's wiki has" >&2 + echo "never been initialized, create its first page once in the Gitea UI" >&2 + echo "(Wiki tab -> New Page), then re-run this script." >&2 + exit 1 +fi + +cp "$wiki_src"/*.md "$workdir/wiki/" + +if git -C "$workdir/wiki" status --porcelain | grep -q .; then + git -C "$workdir/wiki" add -A + git -C "$workdir/wiki" commit --quiet -m "docs: sync from repo docs/wiki (source of truth)" + echo "pushing wiki update for '$repo_name'" + git -C "$workdir/wiki" push --quiet + echo "done: ${#pages[@]} pages synced." +else + echo "done: wiki already up to date; nothing pushed." +fi \ No newline at end of file diff --git a/tests/test_wiki_sync.py b/tests/test_wiki_sync.py new file mode 100644 index 0000000..1efbfc6 --- /dev/null +++ b/tests/test_wiki_sync.py @@ -0,0 +1,112 @@ +"""Checks for the Gitea Wiki sync helper (issue #224). + +The repo-tracked ``docs/wiki/`` stays the source of truth; the helper +mirrors it into the Gitea native wiki only when explicitly invoked. +""" +import os +import subprocess +from pathlib import Path + +REPO_ROOT = Path(__file__).resolve().parent.parent +SCRIPT = REPO_ROOT / "scripts" / "sync-gitea-wiki.sh" + +REQUIRED_PAGES = [ + "Home.md", + "Operator-Guide.md", + "Repositories.md", + "Identity-and-Profiles.md", + "Workflow.md", + "Safety-and-Gates.md", + "MCP-Tools.md", + "Runbooks.md", + "Open-Decisions.md", + "History.md", +] + + +def _run(*args, env_extra=None): + env = dict(os.environ) + env.pop("GITEA_WIKI_SYNC_CONFIRM", None) + if env_extra: + env.update(env_extra) + return subprocess.run( + [str(SCRIPT), *args], capture_output=True, text=True, env=env, + cwd=str(REPO_ROOT), timeout=30) + + +def test_script_exists_and_is_executable(): + assert SCRIPT.is_file(), "missing scripts/sync-gitea-wiki.sh" + assert os.access(SCRIPT, os.X_OK), "sync script is not executable" + + +def test_dry_run_is_default_and_lists_every_page(): + result = _run() + assert result.returncode == 0, result.stderr + out = result.stdout + assert "dry-run" in out.lower() + for page in REQUIRED_PAGES: + assert page in out, f"dry-run plan does not list {page}" + + +def test_dry_run_does_not_push_or_clone(): + result = _run() + blob = (result.stdout + result.stderr).lower() + assert "pushing" not in blob + assert "cloning" not in blob + + +def test_push_refused_without_exact_confirmation(): + result = _run("--push") + assert result.returncode != 0 + blob = result.stdout + result.stderr + assert "GITEA_WIKI_SYNC_CONFIRM" in blob + assert "cloning" not in blob.lower() + + +def test_push_refused_with_wrong_confirmation(): + result = _run("--push", env_extra={"GITEA_WIKI_SYNC_CONFIRM": "yes please"}) + assert result.returncode != 0 + assert "cloning" not in (result.stdout + result.stderr).lower() + + +def test_script_embeds_no_service_urls_or_secrets(): + text = SCRIPT.read_text(encoding="utf-8") + for marker in ("http://", "https://", "token ", "Authorization", "ghp_", "keychain"): + assert marker not in text, f"script embeds {marker!r}" + + +def test_runbook_documents_wiki_sync(): + runbooks = (REPO_ROOT / "docs" / "wiki" / "Runbooks.md").read_text(encoding="utf-8") + assert "sync-gitea-wiki" in runbooks + home = (REPO_ROOT / "docs" / "wiki" / "Home.md").read_text(encoding="utf-8") + assert "source of truth" in home + template = (REPO_ROOT / ".gitea" / "pull_request_template.md").read_text(encoding="utf-8") + assert "sync-gitea-wiki" in template + + +def test_wiki_pages_have_no_local_file_links(): + for path in (REPO_ROOT / "docs" / "wiki").glob("*.md"): + text = path.read_text(encoding="utf-8") + assert "file://" not in text, f"{path.name} contains a file:// link" + + +def test_pr_template_carries_wiki_publication_gate(): + template = (REPO_ROOT / ".gitea" / "pull_request_template.md").read_text(encoding="utf-8") + assert "Wiki tab" in template + assert "#224" in template + assert "not sufficient to close a wiki issue" in template + + +def test_runbooks_document_publication_gate_and_closure_rule(): + runbooks = (REPO_ROOT / "docs" / "wiki" / "Runbooks.md").read_text(encoding="utf-8") + assert "Wiki Publication Readiness Gate (#224)" in runbooks or "Wiki publication readiness gate (#224)" in runbooks.lower() + assert "Closure prevention" in runbooks + assert "GITEA_WIKI_SYNC_CONFIRM" in runbooks + + +def test_repositories_map_tracks_wiki_publication_status(): + repositories = (REPO_ROOT / "docs" / "wiki" / "Repositories.md").read_text(encoding="utf-8") + assert "Wiki publication status" in repositories or "Wiki Publication Status" in repositories + for repo in ("mcp-control-plane", "Gitea-Tools"): + assert repo in repositories + assert "Gitea Wiki published" in repositories \ No newline at end of file