📝 docs(ci): plan scoped tsl playbook sync
This commit is contained in:
@@ -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'
|
||||
```
|
||||
Reference in New Issue
Block a user