fix(update): ship Guide help files in tool update archives

- Un-ignore data/help/** so index.json is included in git archives
- Add ShippedDataTrackingTests to catch future gitignore regressions
- Add Cursor rule for agents editing shipped data/ paths
This commit is contained in:
DazedAnon 2026-07-22 12:22:30 -05:00
parent cb90965f30
commit 41691879dc
5 changed files with 138 additions and 8 deletions

View file

@ -0,0 +1,27 @@
---
description: Shipped data/ assets must be git-trackable so tool updates include them
globs: "{.gitignore,data/**,gui/guide_tab.py,gui/main.py,tests/test_bundled_updates.py}"
alwaysApply: false
---
# Shipped data/ and tool updates
Tool auto-update downloads a **git branch archive**.
Anything matched by `.gitignore` is omitted from that archive and never reaches users.
## When adding or changing shipped defaults under `data/`
- Add a negation under `.gitignore` (same pattern as skills/help):
- `!data/skills/**`
- `!data/help/**`
- `!data/vocab_base.txt`
- `!data/translation_contexts.json`
- Do **not** rely on `UpdateThread.should_install` alone - ignored files never appear in the zip.
- Extend `_SHIPPED_DATA_FILES` / `ShippedDataTrackingTests` in `tests/test_bundled_updates.py` when adding a new shipped path.
- User-local files stay ignored/protected: `data/vocab.txt`, `data/last_update_sha.txt`, `data/wolf_*.json`.
## Quick check
```bash
git check-ignore -- data/help/index.json # must exit 1 (not ignored)
```

1
.gitignore vendored
View file

@ -20,6 +20,7 @@ __pycache__
!assets/engine_icons/**
!.pre-commit-config.yaml
!data/skills/**
!data/help/**
!data/vocab_base.txt
!data/translation_contexts.json
!requirements.txt

37
data/help/index.json Normal file
View file

@ -0,0 +1,37 @@
[
{
"id": "welcome",
"title": "Welcome",
"file": "00-welcome.md"
},
{
"id": "requirements",
"title": "Requirements",
"file": "01-requirements.md"
},
{
"id": "quickstart",
"title": "Quick Start",
"file": "02-quickstart.md"
},
{
"id": "workflow-rpg",
"title": "Workflow: RPG Maker",
"file": "03-workflow-rpg.md"
},
{
"id": "workflow-wolf",
"title": "Workflow: WolfDawn",
"file": "04-workflow-wolf.md"
},
{
"id": "other-tabs",
"title": "Other Tabs",
"file": "05-other-tabs.md"
},
{
"id": "examples",
"title": "Example Walkthrough",
"file": "06-examples.md"
}
]

View file

@ -78,8 +78,8 @@ class UpdateThread(QThread):
PROTECTED_TOP = {".env", "venv", "log", "files", "translated"}
# User-local files under data/ that must not be overwritten.
# Shipped defaults (translation_contexts.json, skills/*.md, vocab_base.txt, …)
# are intentionally updated so tool releases refresh prompts and contexts.
# Shipped defaults (translation_contexts.json, skills/*.md, help/*, vocab_base.txt, …)
# are intentionally updated so tool releases refresh prompts, Guide docs, and contexts.
PROTECTED_DATA_FILES = frozenset({
"data/vocab.txt",
"data/last_update_sha.txt",

View file

@ -7,6 +7,7 @@ a maintainer explicitly uses --force / --refresh-offline / --refresh-all.
from __future__ import annotations
import subprocess
import tempfile
import unittest
from pathlib import Path
@ -14,6 +15,19 @@ from unittest.mock import patch
from util.wolfdawn import WolfDawnError, bundled_binary_path, ensure_wolf_binary
_REPO_ROOT = Path(__file__).resolve().parents[1]
# Shipped with tool updates from the git archive. Keep in sync with .gitignore
# exceptions under data/ — ignored files never appear in update zips.
_SHIPPED_DATA_FILES = (
"data/translation_contexts.json",
"data/skills/system.md",
"data/skills/project_setup.md",
"data/help/index.json",
"data/help/00-welcome.md",
"data/vocab_base.txt",
)
class UpdateThreadInstallFilterTests(unittest.TestCase):
"""Tool update must refresh shipped data/ assets while protecting user state."""
@ -22,12 +36,7 @@ class UpdateThreadInstallFilterTests(unittest.TestCase):
from gui.main import UpdateThread
for rel in (
"data/translation_contexts.json",
"data/skills/system.md",
"data/skills/project_setup.md",
"data/help/index.json",
"data/help/00-welcome.md",
"data/vocab_base.txt",
*_SHIPPED_DATA_FILES,
"gui/main.py",
"gameupdate/GameUpdate.bat",
"gameupdate/gameupdate/patch.ps1",
@ -53,6 +62,62 @@ class UpdateThreadInstallFilterTests(unittest.TestCase):
self.assertFalse(UpdateThread.should_install(Path(rel)))
class ShippedDataTrackingTests(unittest.TestCase):
"""Guide/help and other shipped data/ files must be git-trackable.
Tool updates download a branch archive from git. Anything matched by
.gitignore is omitted from that archive, so Guide markdown and index.json
would never reach end users.
"""
def test_shipped_data_files_exist(self):
for rel in _SHIPPED_DATA_FILES:
with self.subTest(rel=rel):
self.assertTrue(
(_REPO_ROOT / rel).is_file(),
f"missing shipped asset {rel}",
)
def test_help_dir_markdown_matches_index(self):
import json
help_dir = _REPO_ROOT / "data" / "help"
index = json.loads((help_dir / "index.json").read_text(encoding="utf-8"))
for entry in index:
rel = entry["file"]
with self.subTest(file=rel):
self.assertTrue(
(help_dir / rel).is_file(),
f"data/help/index.json references missing {rel}",
)
def test_shipped_data_files_are_not_gitignored(self):
help_dir = _REPO_ROOT / "data" / "help"
rels = list(_SHIPPED_DATA_FILES)
for path in sorted(help_dir.glob("*.md")):
rel = path.relative_to(_REPO_ROOT).as_posix()
if rel not in rels:
rels.append(rel)
for rel in rels:
with self.subTest(rel=rel):
# Plain check-ignore (no -v): exit 0 = ignored, 1 = not ignored.
# -v also reports negation rules with exit 0, which is a false positive.
result = subprocess.run(
["git", "check-ignore", "--", rel],
cwd=_REPO_ROOT,
capture_output=True,
text=True,
check=False,
)
self.assertNotEqual(
result.returncode,
0,
f"{rel} is gitignored; "
"add a !.gitignore exception under data/ so tool updates include it",
)
class CheckToolUpdateTests(unittest.TestCase):
"""Tool update check compares remote SHA to data/last_update_sha.txt only."""