diff --git a/tools/split-s00.sh b/tools/split-s00.sh new file mode 100644 index 0000000..960a6a0 --- /dev/null +++ b/tools/split-s00.sh @@ -0,0 +1,202 @@ +#!/usr/bin/env bash +# +# split-s00.sh — one-off: split issue S-00 (#1) into sub-slices S-00-a..e per +# CLAUDE.md §13. Creates the five sub-slice issues, then closes #1 with a +# comment listing the replacements. +# +# Idempotent: sub-slices already present (matched by title) are skipped; the +# closing comment is posted once (guarded by a marker); #1 is closed only if open. +# +# Usage: +# GITEA_TOKEN= bash tools/split-s00.sh +# +# Token scopes: write:issue. +# +set -euo pipefail + +: "${GITEA_TOKEN:?Set GITEA_TOKEN to a Gitea personal access token (scope: write:issue)}" + +BASE="https://git.labs.respellion.tech/api/v1" +OWNER="eho" +REPO="register-referentie" +REPO_API="$BASE/repos/$OWNER/$REPO" +PARENT=1 # S-00 issue number +MARKER="" + +AUTH=(-H "Authorization: token $GITEA_TOKEN") +JSON=(-H "Content-Type: application/json" -H "Accept: application/json") +say() { printf '%s\n' "$*"; } + +code=$(curl -s -o /dev/null -w '%{http_code}' "${AUTH[@]}" "$REPO_API") +[ "$code" = "200" ] || { say "ERROR: cannot access $OWNER/$REPO (HTTP $code)."; exit 1; } +say "Authenticated against $REPO_API" + +labels_json=$(curl -s "${AUTH[@]}" "$REPO_API/labels?limit=100") +milestones_json=$(curl -s "${AUTH[@]}" "$REPO_API/milestones?state=all&limit=100") +existing_issues=$(curl -s "${AUTH[@]}" "$REPO_API/issues?state=all&type=issues&limit=100") + +MILESTONE="Iteration 0 — Foundations" + +DOD=$(cat <<'EOF' + +## Definition of Done + +- [ ] A linked Gitea issue exists (this one). +- [ ] Failing test written and committed first. +- [ ] Implementation makes the test pass. +- [ ] Refactor commit follows if structure improved. +- [ ] Conventional Commit messages referencing this issue (`refs #NN`). +- [ ] All Gitea Actions CI jobs green. +- [ ] `docker compose up` from a fresh clone reaches green health checks within 3 minutes. +- [ ] Docs touched if behaviour, contracts, or operations changed. +- [ ] ADR added in `docs/architecture/` if a non-obvious decision was made. +- [ ] This issue closed by the merging PR (`closes #NN`). +EOF +) + +create_issue() { + local title="$1" labels_csv="$2" body="$3" + if echo "$existing_issues" | jq -e --arg t "$title" 'any(.[]; .title == $t)' >/dev/null; then + say " skip $title" + return + fi + local ms_id; ms_id=$(echo "$milestones_json" | jq -r --arg m "$MILESTONE" '.[] | select(.title == $m) | .id') + local want label_ids + want=$(printf '%s' "$labels_csv" | jq -R 'split(",")') + label_ids=$(echo "$labels_json" | jq -c --argjson want "$want" '[ .[] | select(.name as $n | $want | index($n)) | .id ]') + local payload + payload=$(jq -n --arg t "$title" --arg b "${body}${DOD}" --argjson ms "$ms_id" --argjson ls "$label_ids" \ + '{title:$t, body:$b, milestone:$ms, labels:$ls}') + curl -s "${AUTH[@]}" "${JSON[@]}" -X POST "$REPO_API/issues" -d "$payload" >/dev/null + say " create $title" +} + +say "" +say "== Sub-slice issues ==" + +create_issue "S-00-a · Placeholder BFF + health endpoint" \ + "type:slice,area:bff" "$(cat <<'EOF' +**Outcome:** A minimal .NET BFF service exposing `GET /health` that returns a green (`Healthy`) status. Runnable with `dotnet run`. + +**Acceptance:** + +- A failing xUnit test asserts `GET /health` returns 200 with a `Healthy` payload (red commit first). +- Implementation makes it pass; `dotnet run` then `curl /health` returns `Healthy`. +- Project follows the §9 layering seed (minimal `Api` project; `Application`/`Domain` added as they earn their place). + +**Touches:** `services/bff/`, tests. + +**Out of scope:** Docker, CI, OIDC, any real endpoints. + +_Split from #1 (S-00) per CLAUDE.md §13._ +EOF +)" + +create_issue "S-00-b · Dockerfile + compose skeleton + compose-up smoke" \ + "type:slice,area:bff,area:infra" "$(cat <<'EOF' +**Outcome:** The BFF is containerized and `infra/docker-compose.yml` brings it up; health goes green within 3 minutes from a fresh clone. + +**Acceptance:** + +- `docker build` of the BFF image succeeds. +- `docker compose up` brings the BFF to a healthy state; a compose-up smoke script curls `/health` and passes. +- README documents the `docker compose up` path. + +**Touches:** `services/bff/Dockerfile`, `infra/docker-compose.yml`, smoke script. + +**Out of scope:** other services; CI wiring (S-00-c). + +_Split from #1 (S-00) per CLAUDE.md §13._ +EOF +)" + +create_issue "S-00-c · Gitea Actions CI (lint, build, unit, compose-up smoke)" \ + "type:slice,area:infra" "$(cat <<'EOF' +**Outcome:** `.gitea/workflows/ci.yaml` runs on PRs and `main` with lint, build, unit tests, and the compose-up smoke test — all green. + +**Acceptance:** + +- Pipeline green on the branch. +- Jobs: lint, build, unit, compose-up smoke. `uses:` references are absolute URLs pinned to a tag (CLAUDE.md §8.7 / §15). +- Self-hosted runner label documented in `docs/runbooks/ci.md`. + +**Touches:** `.gitea/workflows/ci.yaml`, `docs/runbooks/ci.md`. + +**Out of scope:** mutation, e2e, container build + push (later slices). + +_Split from #1 (S-00) per CLAUDE.md §13._ +EOF +)" + +create_issue "S-00-d · Contributor workflow: issue/PR templates, git-cliff, CHANGELOG" \ + "type:slice,area:docs,area:infra" "$(cat <<'EOF' +**Outcome:** Issue templates (`slice`/`bug`/`adr-proposal`), a PR template enforcing the DoD checklist, a `git-cliff` config producing an (empty) `CHANGELOG.md`, and `docs/gitea-workflow.md`. + +**Acceptance:** + +- `.gitea/ISSUE_TEMPLATE/{slice,bug,adr-proposal}.md` and `.gitea/PULL_REQUEST_TEMPLATE.md` present; opening a new issue offers the templates. +- `cliff.toml` present; `git-cliff` generates `CHANGELOG.md`. +- `docs/gitea-workflow.md` documents the issue → milestone → PR flow. + +**Touches:** `.gitea/ISSUE_TEMPLATE/`, `.gitea/PULL_REQUEST_TEMPLATE.md`, `cliff.toml`, `CHANGELOG.md`, `docs/gitea-workflow.md`. + +**Out of scope:** app code. + +_Split from #1 (S-00) per CLAUDE.md §13._ +EOF +)" + +create_issue "S-00-e · Docs scaffold: MkDocs + ADR-0001 + README quickstart" \ + "type:slice,area:docs" "$(cat <<'EOF' +**Outcome:** MkDocs Material builds the `docs/` site; `docs/architecture/adr-0001-loose-coupling.md` exists as the ADR template; README gains a sub-10-minute quickstart. + +**Acceptance:** + +- `mkdocs build` succeeds (`mkdocs.yml` + nav). +- `adr-0001-loose-coupling.md` present, Nygard template. +- A new developer following `README.md` reaches a green local environment in under 10 minutes. + +**Touches:** `mkdocs.yml`, `docs/` nav, `docs/architecture/adr-0001-loose-coupling.md`, `README.md`. + +**Out of scope:** Gitea Pages publish workflow (later). + +_Split from #1 (S-00) per CLAUDE.md §13._ +EOF +)" + +# --- Close #1 with a comment listing the replacements ----------------------- +say "" +say "== Close parent #1 ==" + +# Resolve the numbers of the sub-slices we now have. +all_issues=$(curl -s "${AUTH[@]}" "$REPO_API/issues?state=all&type=issues&limit=100") +refs=$(echo "$all_issues" | jq -r ' + [ .[] | select(.title | startswith("S-00-")) ] + | sort_by(.title) + | map("- #\(.number) — \(.title)") + | join("\n")') + +comments=$(curl -s "${AUTH[@]}" "$REPO_API/issues/$PARENT/comments?limit=100") +if echo "$comments" | jq -e --arg m "$MARKER" 'any(.[]; .body | contains($m))' >/dev/null; then + say " skip split comment (already posted)" +else + body="$MARKER +Split into sub-slices per CLAUDE.md §13. Replacement issues: + +$refs" + curl -s "${AUTH[@]}" "${JSON[@]}" -X POST "$REPO_API/issues/$PARENT/comments" \ + -d "$(jq -n --arg b "$body" '{body:$b}')" >/dev/null + say " create split comment on #$PARENT" +fi + +state=$(echo "$all_issues" | jq -r --argjson n "$PARENT" '.[] | select(.number == $n) | .state') +if [ "$state" = "closed" ]; then + say " skip #$PARENT already closed" +else + curl -s "${AUTH[@]}" "${JSON[@]}" -X PATCH "$REPO_API/issues/$PARENT" \ + -d "$(jq -n '{state:"closed"}')" >/dev/null + say " closed #$PARENT" +fi + +say "" +say "Done."