diff --git a/docs/superpowers/plans/2026-07-13-sync-tsl-playbook-workflow-upgrade.md b/docs/superpowers/plans/2026-07-13-sync-tsl-playbook-workflow-upgrade.md new file mode 100644 index 00000000..f4924149 --- /dev/null +++ b/docs/superpowers/plans/2026-07-13-sync-tsl-playbook-workflow-upgrade.md @@ -0,0 +1,236 @@ +# Sync TSL Playbook Workflow Upgrade Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Make the `tsl-playbook` workflow update only the generated TSL playbook paths while preserving every unrelated target-branch file. + +**Architecture:** Keep the existing temporary clone and external bundle. Replace the broad `AGENTS.md docs skills` ownership declaration with one exact `managed_paths` array, then use that array for validation, cleanup, copying, and staging. + +**Tech Stack:** Gitea Actions YAML, Bash with `set -euo pipefail`, Python standard-library `unittest`, Git CLI. + +## Global Constraints + +- Own only `AGENTS.md`, `docs/tsl/`, `skills/tsl-syntax-reference/`, and `skills/tsl-api-reference/`. +- Preserve root files, non-TSL docs, unrelated Skills, and all other unmanaged target-branch paths. +- Do not special-case `docs/tsl/syntax`; the source tree and builder determine bundle contents. +- Do not merge source and target branches. +- Keep `GIT_ASKPASS`; never embed credentials in the repository URL. +- Build outside the repository before checking out the target branch. +- Do not modify TSL content, the builder, or either Skill. +- Do not execute a TSL interpreter. + +--- + +### Task 1: Enforce exact workflow ownership + +**Files:** +- Modify: `test/test_build_tsl_playbook.py` +- Modify: `.gitea/workflows/sync-tsl-playbook.yml` + +**Interfaces:** +- Consumes: bundle paths `AGENTS.md`, `docs/tsl`, `skills/tsl-syntax-reference`, and `skills/tsl-api-reference`. +- Produces: one `managed_paths` Bash array used by validation, removal, copy, and `git add -A`. +- Preserves: every target-branch path absent from `managed_paths`. + +- [ ] **Step 1: Write the failing static contract assertions** + +Replace `test_sync_workflow_does_not_remove_entire_target_branch` with: + +```python + def test_sync_workflow_does_not_remove_entire_target_branch(self): + text = SYNC_WORKFLOW.read_text(encoding="utf-8") + + self.assertNotRegex(text, r"git rm -rf --quiet\s+\.") + self.assertIn("managed_paths=(", text) + for path in ( + "AGENTS.md", + "docs/tsl", + "skills/tsl-syntax-reference", + "skills/tsl-api-reference", + ): + self.assertIn(f'"{path}"', text) + self.assertNotIn("generated_paths=(AGENTS.md docs skills)", text) + self.assertIn('rm -rf -- "${managed_paths[@]}"', text) + self.assertIn('git add -A -- "${managed_paths[@]}"', text) + self.assertNotIn('cp -R "$bundle"/. "$REPO_DIR"/', text) + self.assertNotIn(".gitea/ci/", text) + self.assertNotIn("https://oauth2", text) + self.assertNotIn("oauth2:${TOKEN}", text) + self.assertNotRegex(text, r"REPO_URL=.*(TOKEN|WORKFLOW)") + self.assertNotIn("git remote set-url", text) + self.assertIn("GIT_ASKPASS", text) + self.assertIn('REPO_URL="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git"', text) +``` + +- [ ] **Step 2: Extend the existing-target integration fixture** + +In `test_sync_preserves_files_outside_generated_paths`, replace the standalone README setup with: + +```python + unmanaged_files = { + "README.md": "manual branch note\n", + "docs/python/index.md": "manual python docs\n", + "skills/manual-skill/SKILL.md": "manual skill\n", + } + for relative, content in unmanaged_files.items(): + path = repo / relative + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(content, encoding="utf-8", newline="\n") + + stale_managed_files = ( + "docs/tsl/stale.md", + "skills/tsl-syntax-reference/stale.md", + "skills/tsl-api-reference/stale.md", + ) + for relative in stale_managed_files: + path = repo / relative + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text("stale\n", encoding="utf-8", newline="\n") + + git(repo, "add", ".") + git(repo, "commit", "-m", "manual target branch content") + git(repo, "push", "-u", "origin", "tsl-playbook") +``` + +- [ ] **Step 3: Assert preservation, cleanup, and idempotence** + +After the first `run_sync(repo)`, add: + +```python + for relative, expected in unmanaged_files.items(): + result = run( + ["git", "show", f"HEAD:{relative}"], + cwd=repo, + check=False, + ) + self.assertEqual(result.returncode, 0, msg=result.stderr) + self.assertEqual(result.stdout, expected) + + for relative in stale_managed_files: + result = run( + ["git", "cat-file", "-e", f"HEAD:{relative}"], + cwd=repo, + check=False, + ) + self.assertNotEqual(result.returncode, 0, msg=relative) + + first_publish = git(repo, "rev-parse", "HEAD").stdout.strip() + git(repo, "checkout", "main") + run_sync(repo) + second_publish = git(repo, "rev-parse", "HEAD").stdout.strip() + self.assertEqual(second_publish, first_publish) +``` + +Keep the existing assertions that generated `AGENTS.md`, `docs/tsl/index.md`, and both TSL Skill entrypoints exist. + +- [ ] **Step 4: Run the focused suite and verify RED** + +```powershell +python -B -m unittest discover -s test -p 'test_build_tsl_playbook.py' -v +``` + +Expected: FAIL because the current workflow has no `managed_paths` array and deletes the whole target `docs` and `skills` directories. + +- [ ] **Step 5: Define the exact managed paths in the workflow** + +Replace `generated_paths=(AGENTS.md docs skills)` with: + +```bash + managed_paths=( + "AGENTS.md" + "docs/tsl" + "skills/tsl-syntax-reference" + "skills/tsl-api-reference" + ) +``` + +Use it for pre-checkout validation: + +```bash + for path in "${managed_paths[@]}"; do + if [ ! -e "$bundle/$path" ]; then + echo "ERROR: bundle is missing expected path: $path" >&2 + exit 1 + fi + done +``` + +- [ ] **Step 6: Implement exact cleanup, copying, and staging** + +Replace the broad removal, bundle-root copy, and staging commands with: + +```bash + rm -rf -- "${managed_paths[@]}" + for path in "${managed_paths[@]}"; do + mkdir -p "$(dirname "$path")" + cp -R -- "$bundle/$path" "$path" + done + + git add -A -- "${managed_paths[@]}" +``` + +Do not add a `docs/tsl/syntax` condition or any other path exception. + +- [ ] **Step 7: Run the focused suite and verify GREEN** + +```powershell +python -B -m unittest discover -s test -p 'test_build_tsl_playbook.py' -v +``` + +Expected: all 7 tests pass. The integration test proves unmanaged root/docs/skills files survive, stale managed files disappear, and a second identical publication creates no commit. + +- [ ] **Step 8: Inspect and commit the implementation** + +```powershell +git diff --check +git diff -- .gitea/workflows/sync-tsl-playbook.yml test/test_build_tsl_playbook.py +git add .gitea/workflows/sync-tsl-playbook.yml test/test_build_tsl_playbook.py +git commit -m ':wrench: chore(ci): scope tsl playbook sync paths' +``` + +Expected: the commit contains only the workflow and its regression tests. + +--- + +### Task 2: Final workflow verification + +**Files:** +- Verify: `.gitea/workflows/sync-tsl-playbook.yml` +- Verify: `test/test_build_tsl_playbook.py` + +**Interfaces:** +- Consumes: Task 1's exact managed-path publication flow. +- Produces: fresh evidence that the workflow is scoped, secure, idempotent, and compatible with new target branches. + +- [ ] **Step 1: Run the full focused suite in a clean process** + +```powershell +python -B -m unittest discover -s test -p 'test_build_tsl_playbook.py' -v +``` + +Expected: 7 tests pass with `OK` and no errors or failures. + +- [ ] **Step 2: Verify the ownership contract directly** + +```powershell +rg -n 'managed_paths|generated_paths|rm -rf|cp -R|git add -A' .gitea/workflows/sync-tsl-playbook.yml +``` + +Expected: one four-entry `managed_paths` definition; no broad `generated_paths`; removal, copy, and staging derive from `managed_paths`; no whole-bundle copy. + +- [ ] **Step 3: Verify repository state** + +```powershell +git status --short +git show --stat --oneline HEAD +``` + +Expected: the implementation commit contains only `.gitea/workflows/sync-tsl-playbook.yml` and `test/test_build_tsl_playbook.py`, with a clean working tree. + +- [ ] **Step 4: Avoid a verification-only commit** + +If verification passes without corrections, stop. If it exposes a defect, return to Task 1's RED → GREEN cycle and commit only the correction: + +```powershell +git commit -m ':bug: fix(ci): close tsl playbook sync verification gap' +```