Restore an iteration as the current skill. If ``n`` is ``None``, restore the highest-numbered iteration. Raises ``FileNotFoundError`` if the requested iteration doesn't exist. Returns the path to the restored skill directory.
(kb_dir: Path, skill_name: str, n: int | None = None)
| 84 | |
| 85 | |
| 86 | def restore_iteration(kb_dir: Path, skill_name: str, n: int | None = None) -> Path: |
| 87 | """Restore an iteration as the current skill. |
| 88 | |
| 89 | If ``n`` is ``None``, restore the highest-numbered iteration. Raises |
| 90 | ``FileNotFoundError`` if the requested iteration doesn't exist. |
| 91 | |
| 92 | Returns the path to the restored skill directory. |
| 93 | """ |
| 94 | iters = list_iterations(kb_dir, skill_name) |
| 95 | if not iters: |
| 96 | raise FileNotFoundError(f"No iterations exist for skill {skill_name!r}.") |
| 97 | |
| 98 | if n is None: |
| 99 | src = iters[-1] |
| 100 | else: |
| 101 | match = next( |
| 102 | (p for p in iters if _iter_number(p) == n), |
| 103 | None, |
| 104 | ) |
| 105 | if match is None: |
| 106 | raise FileNotFoundError(f"Iteration {n} not found for skill {skill_name!r}.") |
| 107 | src = match |
| 108 | |
| 109 | # Save the current state before overwriting it — rollback is a mutation |
| 110 | # too, and the workspace promise ("no work is destroyed") has to hold |
| 111 | # in both directions. A user who edits files in chat then rolls back |
| 112 | # gets those edits preserved as the next iteration, not silently lost. |
| 113 | dest = _skill_dir(kb_dir, skill_name) |
| 114 | if dest.exists(): |
| 115 | save_iteration(kb_dir, skill_name) |
| 116 | shutil.rmtree(dest) |
| 117 | shutil.copytree(src, dest) |
| 118 | return dest |
| 119 | |
| 120 | |
| 121 | # --------------------------------------------------------------------------- |