feat(playbook): version durable scratch workflow state

This commit is contained in:
csh
2026-08-14 17:11:54 +08:00
parent 9487dbddf1
commit 8f1056130d
8 changed files with 237 additions and 17 deletions
+76
View File
@@ -108,6 +108,11 @@ def seed_custom_files(project_root: Path) -> None:
encoding="utf-8",
newline="\n",
)
(project_root / ".gitignore").write_text(
"build/\n.scratch/\n",
encoding="utf-8",
newline="\n",
)
class PlaybookDeploymentTests(unittest.TestCase):
@@ -254,6 +259,7 @@ project_root = "C:\workspace\project"
"AGENT_RULES.md",
"AGENT_RULES.local.md",
"CLAUDE.md",
".gitignore",
".gitattributes",
"memory-bank/project-brief.md",
"memory-bank/system-patterns.md",
@@ -332,6 +338,22 @@ project_root = "C:\workspace\project"
f"`{playbook_root.as_posix()}/` 是 Playbook 模板/供应商目录",
rules_text,
)
gitignore_text = (project_root / ".gitignore").read_text(
encoding="utf-8"
)
self.assertIn("build/", gitignore_text)
self.assertEqual(
gitignore_text.count("# BEGIN playbook .scratch runtime"),
1,
)
self.assertEqual(
gitignore_text.count("# END playbook .scratch runtime"),
1,
)
self.assertIn("!/.scratch/**", gitignore_text)
self.assertIn("/.scratch/*.lock", gitignore_text)
self.assertIn("/.scratch/worktrees/", gitignore_text)
self.assertIn("/.scratch/**/*.tmp", gitignore_text)
if install_mode == "snapshot":
snapshot_root = project_root / playbook_root
@@ -345,6 +367,9 @@ project_root = "C:\workspace\project"
self.assertTrue(
(snapshot_root / "playbook.example.toml").is_file()
)
self.assertTrue(
(snapshot_root / "templates/gitignore.template").is_file()
)
self.assertFalse(
(snapshot_root / "playbook.toml.example").exists()
)
@@ -355,6 +380,57 @@ project_root = "C:\workspace\project"
else:
self.assertFalse((source_root / "SOURCE.md").exists())
def test_sync_rules_gitignore_block_tracks_durable_scratch_only(self):
with tempfile.TemporaryDirectory() as tmp_dir:
project_root = Path(tmp_dir) / "project"
project_root.mkdir()
seed_custom_files(project_root)
initialized = subprocess.run(
["git", "init", "-q"],
cwd=project_root,
capture_output=True,
text=True,
)
self.assertEqual(initialized.returncode, 0, msg=initialized.stderr)
playbook_root = MODE_ROOTS["snapshot"]
config = write_config(project_root, "snapshot", playbook_root)
result = run_playbook(SCRIPT, config, project_root)
self.assertEqual(result.returncode, 0, msg=f"{result.stdout}{result.stderr}")
paths = {
"queue": project_root / ".scratch/queue.md",
"spec": project_root / ".scratch/alpha/spec.md",
"evidence": project_root / ".scratch/alpha/evidence/check.json",
"lock": project_root / ".scratch/.main-loop.lock",
"other_lock": project_root / ".scratch/worker.lock",
"worktree": project_root / ".scratch/worktrees/alpha/file.txt",
"temp": project_root / ".scratch/alpha/.spec.md.deadbeef.tmp",
}
for path in paths.values():
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text("test\n", encoding="utf-8", newline="\n")
def is_ignored(path: Path) -> bool:
checked = subprocess.run(
[
"git",
"check-ignore",
"-q",
"--",
path.relative_to(project_root).as_posix(),
],
cwd=project_root,
capture_output=True,
text=True,
)
return checked.returncode == 0
for name in ("queue", "spec", "evidence"):
self.assertFalse(is_ignored(paths[name]), msg=f"{name} must be tracked")
for name in ("lock", "other_lock", "worktree", "temp"):
self.assertTrue(is_ignored(paths[name]), msg=f"{name} must be ignored")
def test_sync_without_standards_keeps_language_rules(self):
with tempfile.TemporaryDirectory() as tmp_dir:
project_root = Path(tmp_dir) / "project"