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
+75 -1
View File
@@ -339,7 +339,12 @@ def install_snapshot(config: dict, context: dict) -> int:
if templates_ci.is_dir():
copytree(templates_ci, templates_dst / "ci")
for name in ("AGENTS.template.md", "AGENT_RULES.template.md", "README.md"):
for name in (
"AGENTS.template.md",
"AGENT_RULES.template.md",
"gitignore.template",
"README.md",
):
src = templates_root / name
if src.is_file():
copy2(src, templates_dst / name)
@@ -510,6 +515,8 @@ _AGENTS_BLOCK_START = "<!-- playbook:agents:start -->"
_AGENTS_BLOCK_END = "<!-- playbook:agents:end -->"
_RULES_BLOCK_START = "<!-- playbook:rules:start -->"
_RULES_BLOCK_END = "<!-- playbook:rules:end -->"
_GITIGNORE_BLOCK_START = "# BEGIN playbook .scratch runtime"
_GITIGNORE_BLOCK_END = "# END playbook .scratch runtime"
def replace_marked_block(
@@ -751,6 +758,10 @@ def sync_rules_action(config: dict, context: dict) -> int:
if not rules_src.is_file():
print(f"ERROR: template not found: {rules_src}", file=sys.stderr)
return 2
gitignore_src = templates_dir / "gitignore.template"
if not gitignore_src.is_file():
print(f"ERROR: template not found: {gitignore_src}", file=sys.stderr)
return 2
rules_dst = project_root / "AGENT_RULES.md"
force = bool(config.get("force", False))
@@ -809,6 +820,16 @@ def sync_rules_action(config: dict, context: dict) -> int:
)
log("Created: AGENT_RULES.local.md")
try:
sync_gitignore_block(
gitignore_src,
project_root / ".gitignore",
no_backup,
)
except ValueError as exc:
print(f"ERROR: {exc}", file=sys.stderr)
return 2
return 0
@@ -1046,6 +1067,59 @@ def sync_gitattributes_block(src: Path, dst: Path, no_backup: bool) -> None:
log("Synced .gitattributes from standards (block).")
def sync_gitignore_block(src: Path, dst: Path, no_backup: bool) -> None:
source_lines = src.read_text(encoding="utf-8").splitlines()
block_lines = [
_GITIGNORE_BLOCK_START,
*source_lines,
_GITIGNORE_BLOCK_END,
]
original_text = dst.read_text(encoding="utf-8") if dst.exists() else ""
if dst.exists():
original_lines = original_text.splitlines()
start_positions = [
index
for index, line in enumerate(original_lines)
if line.strip() == _GITIGNORE_BLOCK_START
]
end_positions = [
index
for index, line in enumerate(original_lines)
if line.strip() == _GITIGNORE_BLOCK_END
]
if (
len(start_positions) != len(end_positions)
or len(start_positions) > 1
or (
start_positions
and start_positions[0] >= end_positions[0]
)
):
raise ValueError(".gitignore has an invalid playbook .scratch block")
if start_positions:
updated_text = replace_marked_block(
original_text,
block_lines,
_GITIGNORE_BLOCK_START,
_GITIGNORE_BLOCK_END,
)
else:
content = original_text.rstrip("\n")
if content:
content += "\n\n"
updated_text = content + "\n".join(block_lines) + "\n"
else:
updated_text = "\n".join(block_lines) + "\n"
if dst.exists() and updated_text == original_text:
log("Unchanged: .gitignore (playbook .scratch block)")
return
backup_path(dst, no_backup)
dst.write_text(updated_text, encoding="utf-8", newline="\n")
log("Synced: .gitignore (playbook .scratch block)")
def sync_standards_action(config: dict, context: dict) -> int:
if "langs" not in config:
print("ERROR: langs is required for sync_standards", file=sys.stderr)