General-purpose coding policy for Baruch's AI agents
74
93%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
Medium
Suggest reviewing before use
"""Move the owner's state and config homes from `teamlead` to `foreman` (#501).
The skill, CLI and package were renamed from teamlead to foreman with no
alias. The operator's durable homes move with them:
- `$XDG_STATE_HOME/teamlead/` -> `$XDG_STATE_HOME/foreman/`
- `$XDG_CONFIG_HOME/teamlead/` -> `$XDG_CONFIG_HOME/foreman/`
Records keep their contents, field names and `schema_version`s. Two things
make this more than a directory move:
- Owner stores carry a `state_path` identity field that must equal the
canonical state path, or the store refuses to load. `migrate` rewrites
every `state_path` field holding the old canonical path to the new one. A
pending retrospective journal's derived ancestry digest is rebased with
that identity; no record shape or version changes
- Records also quote old absolute paths as history: stow required reads,
retrospective notes, attention evidence. Rewriting history is forbidden, so
the old home is left as a symlink to the new one and every quoted path
still resolves
A home is in one of five states (`status`):
- `absent` -- neither directory exists
- `legacy` -- only the old directory exists: migrate it
- `current` -- the new directory exists and the old one is absent or a
symlink to it. Migrating again finishes a move a crash
interrupted (symlink, identity fields) and changes nothing else
- `split` -- both exist as separate directories, or the old one is a
symlink elsewhere. Refused, never merged
- `blocked` -- the old path is neither a directory nor a symlink
Any other command whose default home is `legacy` refuses and names
`migrate-home` (`require_current`); it never starts an empty store at the new
path. A command given explicit `--state`/`--config` paths is unaffected.
Every other command reading a default home holds the home guard, `$XDG_STATE_HOME/.foreman-home.lock`,
shared for its whole run (`guard`). The guard lives beside both homes, never
inside one, so it stays put while they move. `migrate` takes it exclusively
before it looks at either home and holds it through both moves, creating
the state root first when only a legacy config home exists: it refuses
while any command holds the guard, and a command that starts mid-migration is
refused instead of reading a half-moved home. It also refuses while any owner
lock in the old state home is held, for a running foreman older than the guard.
A failed rename or link raises `StateError` naming what moved; a re-run
finishes it.
"""
import copy
import fcntl
import hashlib
import json
import os
from contextlib import ExitStack, contextmanager
from pathlib import Path
from . import runnable
from .errors import StateError, UsageError
from .state import save_state
LEGACY = "teamlead"
CURRENT = "foreman"
STATE_FILE = "state.json"
GUARD = ".foreman-home.lock"
def roots(environ=None):
environ = os.environ if environ is None else environ
state = environ.get("XDG_STATE_HOME")
config = environ.get("XDG_CONFIG_HOME")
return {
"state": Path(state) if state else Path.home() / ".local" / "state",
"config": Path(config) if config else Path.home() / ".config",
}
def pair(root):
return root / LEGACY, root / CURRENT
def status(root):
old, new = pair(root)
if old.is_symlink():
target = Path(os.path.realpath(old))
if new.is_dir() and target == Path(os.path.realpath(new)):
return "current"
return "split"
if old.exists() and not old.is_dir():
return "blocked"
if old.is_dir():
return "split" if new.exists() else "legacy"
return "current" if new.is_dir() else "absent"
@contextmanager
def guard(exclusive, environ=None):
"""Hold the home guard: exclusive for `migrate`, shared for every other command."""
root = roots(environ)["state"]
path = root / GUARD
if not root.is_dir():
if not exclusive:
# A command never creates the root. Without it there is no state home
# to move, and a legacy config home refuses the command before it
# reads anything (`require_current`), so it has nothing to protect.
yield
return
# `migrate` writes anyway, and a legacy config home can exist without a
# state root: create the root so a command starting mid-migration meets
# the guard.
try:
root.mkdir(parents=True, exist_ok=True)
except OSError as exc:
raise StateError("Cannot create the state root {}: {}. Restore access to it, then run `{}` "
"again; nothing was moved.".format(root, exc, runnable.command("migrate-home")), {"root": str(root)}) from None
try:
handle = path.open("a", encoding="utf-8")
except OSError as exc:
raise StateError("Cannot open the home guard {}: {}. Restore access to {}, then run the command again; "
"nothing was read or written.".format(path, exc, root), {"guard": str(path)}) from None
with handle:
try:
fcntl.flock(handle.fileno(), (fcntl.LOCK_EX if exclusive else fcntl.LOCK_SH) | fcntl.LOCK_NB)
except BlockingIOError:
if exclusive:
raise UsageError("A foreman command is running (it holds {}). Stop every foreman and let running "
"commands finish, then run `{}` again; nothing was moved.".format(path, runnable.command("migrate-home")),
{"guard": str(path)}) from None
raise UsageError("migrate-home is moving the foreman homes (it holds {}). Wait for it to finish, then run "
"this command again; nothing was read or written.".format(path), {"guard": str(path)}) from None
except OSError as exc:
raise StateError("Cannot lock the home guard {}: {}. Use a filesystem supporting process locks; nothing "
"was read or written.".format(path, exc), {"guard": str(path)}) from None
yield
def require_current(kinds, environ=None):
"""Refuse a default home still at the legacy path; `kinds` names the defaults a command uses."""
for kind, root in roots(environ).items():
if kind not in kinds:
continue
state = status(root)
if state == "legacy":
old, new = pair(root)
raise StateError("The {} home is still at {}. Stop every foreman, then run `{}` to move it "
"to {}; nothing was read or written.".format(kind, old, runnable.command("migrate-home"), new), {"legacy": str(old), "current": str(new)})
if state in ("split", "blocked"):
raise StateError(_refusal(kind, root, state), {"legacy": str(pair(root)[0]), "current": str(pair(root)[1])})
def _refusal(kind, root, state):
old, new = pair(root)
if state == "blocked":
return "The legacy {} home {} is not a directory; move it aside, then run `{}`.".format(kind, old, runnable.command("migrate-home"))
return ("Both {} and {} exist as separate {} homes. They are never merged; keep the one holding the live "
"records, move the other aside, then run `{}`.".format(old, new, kind, runnable.command("migrate-home")))
def _held_locks(directory):
"""Take every owner lock in the home without blocking; a held one refuses."""
stack = ExitStack()
for lock_path in sorted(directory.rglob("*.lock")):
try:
handle = stack.enter_context(lock_path.open("a", encoding="utf-8"))
fcntl.flock(handle.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
except BlockingIOError:
stack.close()
raise UsageError("A foreman command holds {}. Stop every foreman and let running commands finish, then "
"run `{}` again; nothing was moved.".format(lock_path, runnable.command("migrate-home")),
{"lock": str(lock_path)}) from None
except OSError as exc:
stack.close()
raise StateError("Cannot lock {}: {}. Restore access to the state home, then run `{}` again; "
"nothing was moved.".format(lock_path, exc, runnable.command("migrate-home")), {"lock": str(lock_path)}) from None
return stack
def _rewrite_identity(value, old, new):
"""Replace `state_path` fields equal to `old`; return (value, count)."""
if isinstance(value, dict):
count = 0
for key, item in value.items():
if key == "state_path" and item == old:
value[key] = new
count += 1
else:
value[key], found = _rewrite_identity(item, old, new)
count += found
return value, count
if isinstance(value, list):
count = 0
for index, item in enumerate(value):
value[index], found = _rewrite_identity(item, old, new)
count += found
return value, count
return value, 0
def _digest(value):
return hashlib.sha256(json.dumps(value, sort_keys=True, separators=(",", ":")).encode()).hexdigest()
def _retrospective_ancestry(index, pending):
"""Return the index state whose digest a valid pending journal records."""
record = pending.get("record") if isinstance(pending, dict) else None
records = index.get("records") if isinstance(index, dict) else None
if not isinstance(record, dict) or not isinstance(records, list):
return None
committed_index = next((offset for offset, entry in enumerate(records)
if isinstance(entry, dict) and entry.get("id") == record.get("id")), None)
if committed_index is None:
return index
committed = records[committed_index]
if committed != record:
return None
return {**index, "records": records[:committed_index] + records[committed_index + 1:]}
def _rebase_retrospective_pending(path, document, old_state, new_state):
"""Rebase a retrospective journal across the index identity rewrite."""
if not path.parent.name.endswith(".retrospectives"):
return None
pending_path = path.parent / "pending.json"
if path.name == "index.json":
if not pending_path.is_file() or pending_path.is_symlink():
return None
try:
pending = json.loads(pending_path.read_text(encoding="utf-8"))
except (OSError, UnicodeDecodeError, json.JSONDecodeError) as exc:
raise StateError("Cannot read {} while moving the state home: {}. Restore the file, then run `{}` "
"again; files already rewritten stay valid.".format(
pending_path, exc, runnable.command("migrate-home")), {"path": str(pending_path)}) from None
index = document
elif path.name == "pending.json" and not (path.parent / "index.json").exists():
pending = document
index = {"schema_version": 1, "state_path": old_state,
"baseline_at": None, "records": [], "transitions": []}
else:
return None
if not isinstance(pending, dict) or not isinstance(pending.get("previous_index"), str):
return None
legacy = copy.deepcopy(index)
current = copy.deepcopy(index)
legacy, _ = _rewrite_identity(legacy, new_state, old_state)
current, _ = _rewrite_identity(current, old_state, new_state)
legacy_ancestry = _retrospective_ancestry(legacy, pending)
current_ancestry = _retrospective_ancestry(current, pending)
if legacy_ancestry is None or current_ancestry is None:
return None
previous = pending["previous_index"]
current_digest = _digest(current_ancestry)
if previous == current_digest:
return None
if previous != _digest(legacy_ancestry):
return None
pending["previous_index"] = current_digest
save_state(pending_path, pending)
return {"path": str(pending_path), "fields": 1}
def _rewrite_home(home, old_state, new_state):
rewritten = []
for path in sorted(home.rglob("*.json")):
if path.is_symlink() or not path.is_file():
continue
try:
document = json.loads(path.read_text(encoding="utf-8"))
except (OSError, UnicodeDecodeError, json.JSONDecodeError) as exc:
raise StateError("Cannot read {} while moving the state home: {}. Restore the file, then run `{}` "
"again; files already rewritten stay valid.".format(path, exc, runnable.command("migrate-home")), {"path": str(path)}) from None
rebased = _rebase_retrospective_pending(path, document, old_state, new_state)
if rebased:
rewritten.append(rebased)
document, count = _rewrite_identity(document, old_state, new_state)
if count:
save_state(path, document)
rewritten.append({"path": str(path), "fields": count})
return rewritten
def _move(kind, root, rewrite):
old, new = pair(root)
state = status(root)
if state in ("split", "blocked"):
raise UsageError(_refusal(kind, root, state), {"legacy": str(old), "current": str(new)})
if state == "absent":
return {"kind": kind, "status": "absent", "moved": False, "rewritten": []}
moved = False
with ExitStack() as stack:
# Lock fds follow the directory through the rename.
stack.enter_context(_held_locks(old if state == "legacy" else new))
if state == "legacy":
try:
os.rename(old, new)
except OSError as exc:
raise StateError("Cannot move the {} home {} to {}: {}. Nothing was moved; restore access to {}, then "
"run `{}` again.".format(kind, old, new, exc, root, runnable.command("migrate-home")),
{"legacy": str(old), "current": str(new)}) from None
moved = True
if not old.is_symlink():
try:
os.symlink(new, old, target_is_directory=True)
except OSError as exc:
raise StateError("The {} home moved to {}, but linking {} to it failed: {}. Quoted history under the "
"old path does not resolve until the link exists; restore access to {}, then run "
"`{}` again to finish.".format(kind, new, old, exc, root, runnable.command("migrate-home")),
{"legacy": str(old), "current": str(new), "moved": moved}) from None
rewritten = []
if rewrite:
# Stores recorded the canonical path, so compare against the resolved root.
base = root.resolve()
rewritten = _rewrite_home(new, str(base / LEGACY / STATE_FILE), str(base / CURRENT / STATE_FILE))
return {"kind": kind, "status": "current", "moved": moved, "legacy": str(old), "current": str(new), "rewritten": rewritten}
def migrate(environ=None):
"""Move both homes; idempotent, and a crash midway is finished by a re-run."""
homes = roots(environ)
with guard(True, environ):
for kind, root in homes.items():
state = status(root)
if state in ("split", "blocked"):
raise UsageError(_refusal(kind, root, state), {"legacy": str(pair(root)[0]), "current": str(pair(root)[1])})
return {"schema_version": 1, "homes": [_move("state", homes["state"], True), _move("config", homes["config"], False)]}.tessl-plugin
hooks
rules
skills
adopt-fork-pr
herdr-foreman
classify
foreman
references
specialists
templates
tests
herdr-standup
migrate-to-plugin
onboard-repo
release
references
tests