fix(transcend): skip repository copies in scratchpads
A scan on the real host offered 4276 candidates, 4253 of them files of two repository copies in one peer's scratchpad. scratch_candidates now walks with os.walk, skips any directory holding .git, roles, plugins, pyproject.toml, package.json or a Makefile, and descends at most one level below a scratchpad; the real scan drops to 59 candidates. test_transcend adds a nested helper that stays, a deeper one and a repository copy that drop out (negative control against the unfiltered scan fails). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
194f5e28bd
commit
3e2f7a7ddb
3 files changed
+25
-6
No files matched your search
@@ -19,7 +19,9 @@ transcend.py mark --state <same> <path> carried|dropped|later
|
||||
scratchpad, every peer session's scratchpad, and the agent homes
|
||||
(`~/.claude`, `~/.agents`, `~/.codex`) for skills, commands, agents, hooks
|
||||
and rules the skills repository does not ship. It keeps scripts, tests and
|
||||
skills, and skips files named like secrets and files over 1 MB. Carried or
|
||||
skills at most one directory below a scratchpad, and skips files named like
|
||||
secrets, files over 1 MB, and repository copies (a directory holding `.git`,
|
||||
`roles`, `plugins`, `pyproject.toml`, `package.json` or a `Makefile`). Carried or
|
||||
dropped candidates return only when their content changes; `later` returns
|
||||
every time.
|
||||
|
||||
|
||||
@@ -22,6 +22,8 @@ SECRET_NAME = re.compile(
|
||||
r"(secret|token|credential|passw|auth|\.env$|id_[a-z0-9]+$|\.pem$|\.key$)", re.IGNORECASE
|
||||
)
|
||||
MAX_BYTES = 1_000_000
|
||||
MAX_DEPTH = 1
|
||||
REPO_MARKERS = {".git", "roles", "plugins", "pyproject.toml", "package.json", "Makefile"}
|
||||
HOME_SOURCES = {
|
||||
".claude/skills": "skill",
|
||||
".agents/skills": "skill",
|
||||
@@ -74,11 +76,21 @@ def session_names(home: Path) -> dict[str, str]:
|
||||
|
||||
def scratch_candidates(root: Path, source: str, owner: str) -> list[dict]:
|
||||
found = []
|
||||
for path in sorted(p for p in root.rglob("*") if p.is_file()):
|
||||
kind = file_kind(path)
|
||||
if kind:
|
||||
found.append({"source": source, "owner": owner, "kind": kind, "path": str(path)})
|
||||
return found
|
||||
for current, dirs, files in os.walk(root):
|
||||
here = Path(current)
|
||||
depth = len(here.relative_to(root).parts)
|
||||
if depth and REPO_MARKERS & {*dirs, *files}:
|
||||
dirs.clear()
|
||||
continue
|
||||
if depth >= MAX_DEPTH:
|
||||
dirs.clear()
|
||||
for name in files:
|
||||
kind = file_kind(here / name)
|
||||
if kind:
|
||||
found.append(
|
||||
{"source": source, "owner": owner, "kind": kind, "path": str(here / name)}
|
||||
)
|
||||
return sorted(found, key=lambda item: item["path"])
|
||||
|
||||
|
||||
def shipped(skills_repo: Path) -> set[str]:
|
||||
|
||||
@@ -40,6 +40,10 @@ class TestTranscend(unittest.TestCase):
|
||||
self._write(self.own / "auth_token.py", "secret")
|
||||
self._write(self.own / "big.py", "x" * 1_000_001)
|
||||
self._write(self.peer / "helper.sh", "echo hi")
|
||||
self._write(self.peer / "probe" / "nested.py", "print(2)")
|
||||
self._write(self.peer / "probe" / "deep" / "too_deep.py", "print(3)")
|
||||
self._write(self.peer / "copy" / "pyproject.toml", "[project]")
|
||||
self._write(self.peer / "copy" / "copied.py", "print(4)")
|
||||
for name in ("kvb-x", "caveman", "custom"):
|
||||
self._write(self.home / ".claude" / "skills" / name / "SKILL.md", name)
|
||||
self._write(self.home / ".codex" / "rules" / "default.rules", "allow")
|
||||
@@ -78,6 +82,7 @@ class TestTranscend(unittest.TestCase):
|
||||
"probe.py": ("own", "self", "script"),
|
||||
"test_probe.py": ("own", "self", "test"),
|
||||
"helper.sh": ("peer", "Peer", "script"),
|
||||
"nested.py": ("peer", "Peer", "script"),
|
||||
"custom": ("home", "operator", "skill"),
|
||||
"default.rules": ("home", "operator", "rule"),
|
||||
},
|
||||
|
||||
Reference in new issue
Block a user