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:
parent
cb90965f30
commit
41691879dc
5 changed files with 138 additions and 8 deletions
27
.cursor/rules/shipped-data-assets.mdc
Normal file
27
.cursor/rules/shipped-data-assets.mdc
Normal 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
1
.gitignore
vendored
|
|
@ -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
37
data/help/index.json
Normal 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"
|
||||
}
|
||||
]
|
||||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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."""
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue