Studio: remove AGENTS.md and CLAUDE.md from install artifacts (#7096)
* Studio: remove AGENTS.md from install artifacts * Studio: prune CLAUDE.md from install artifacts * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fix Studio instruction cleanup edge cases * Trim Studio cleanup comments * Make Studio cleanup safe on PowerShell 5.1 * Fix Studio cleanup ownership boundaries * Simplify Windows link detection --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
parent
ca979e9643
commit
9e77c1e663
5 changed files with 373 additions and 3 deletions
|
|
@ -1,4 +1,4 @@
|
|||
# i18n Contribution Instructions
|
||||
# i18n Contribution Guide
|
||||
|
||||
- `locales/en.ts` is the complete baseline message file.
|
||||
- Non-English locale files may be partial. Missing keys must fall back to English at runtime.
|
||||
|
|
@ -9,4 +9,4 @@
|
|||
- Keep product and technical names unchanged unless there is an established localized name, for example `Unsloth Studio`, `LoRA`, `GGUF`, and `Hugging Face`.
|
||||
- Keep translation changes small and reviewable. Prefer separate commits for runtime changes, UI migration, and locale text.
|
||||
- When adding user-facing Studio UI text, add the English message key first and add non-English overrides only when the translation is clear.
|
||||
- Run `npx tsx src/i18n/check-parity.ts` before committing to ensure there are no shape mismatches or placeholder discrepancies in the non-English overlays.
|
||||
- Run `npx tsx src/i18n/check-parity.ts` before committing to ensure there are no shape mismatches or placeholder discrepancies in the non-English overlays.
|
||||
|
|
@ -20,6 +20,7 @@ import re
|
|||
import shutil
|
||||
import site
|
||||
import socket
|
||||
import stat
|
||||
import struct
|
||||
import subprocess
|
||||
import sys
|
||||
|
|
@ -4385,6 +4386,48 @@ def copy_directory_contents(source_dir: Path, destination: Path) -> None:
|
|||
shutil.copy2(item, target)
|
||||
|
||||
|
||||
def _is_link_or_junction(path: Path) -> bool:
|
||||
"""Return whether ``path`` redirects to another filesystem location."""
|
||||
if os.name == "nt":
|
||||
try:
|
||||
attributes = getattr(path.lstat(), "st_file_attributes", 0)
|
||||
except OSError:
|
||||
return True
|
||||
return bool(attributes & stat.FILE_ATTRIBUTE_REPARSE_POINT)
|
||||
try:
|
||||
return path.is_symlink()
|
||||
except OSError:
|
||||
return True
|
||||
|
||||
|
||||
def remove_agent_instruction_files(root: Path) -> int:
|
||||
"""Best-effort removal inside a managed tree without following links."""
|
||||
if _is_link_or_junction(root) or not root.is_dir():
|
||||
return 0
|
||||
|
||||
removed = 0
|
||||
for current_dir, dirnames, filenames in os.walk(root, topdown = True, followlinks = False):
|
||||
current_path = Path(current_dir)
|
||||
# followlinks=False still follows Windows junctions.
|
||||
if current_path != root and _is_link_or_junction(current_path):
|
||||
dirnames.clear()
|
||||
continue
|
||||
dirnames[:] = [
|
||||
dirname for dirname in dirnames if not _is_link_or_junction(current_path / dirname)
|
||||
]
|
||||
for filename in sorted({"AGENTS.md", "CLAUDE.md"}.intersection(filenames)):
|
||||
candidate = current_path / filename
|
||||
try:
|
||||
candidate.unlink()
|
||||
except FileNotFoundError:
|
||||
continue
|
||||
except OSError as exc:
|
||||
log(f"could not remove contributor-only instruction {candidate}: {exc}")
|
||||
else:
|
||||
removed += 1
|
||||
return removed
|
||||
|
||||
|
||||
def hydrate_source_tree(
|
||||
source_ref: str,
|
||||
install_dir: Path,
|
||||
|
|
@ -4447,6 +4490,9 @@ def hydrate_source_tree(
|
|||
"upstream source archive was missing required repo files: " + ", ".join(missing)
|
||||
)
|
||||
copy_directory_contents(source_root, install_dir)
|
||||
removed = remove_agent_instruction_files(install_dir)
|
||||
if removed:
|
||||
log(f"removed {removed} contributor-only agent instruction file(s) from staged source")
|
||||
except PrebuiltFallback:
|
||||
raise
|
||||
except Exception as exc:
|
||||
|
|
@ -6824,6 +6870,7 @@ def install_prebuilt(
|
|||
override_has_rocm: bool = False,
|
||||
override_rocm_gfx: str | None = None,
|
||||
force_cpu: bool = False,
|
||||
instruction_cleanup_root: Path | None = None,
|
||||
) -> None:
|
||||
host = detect_host()
|
||||
host = _apply_host_overrides(
|
||||
|
|
@ -6836,8 +6883,15 @@ def install_prebuilt(
|
|||
host, published_repo, published_release_tag, force_cpu = force_cpu
|
||||
)
|
||||
choice: AssetChoice | None = None
|
||||
cleanup_root = install_dir if instruction_cleanup_root is None else instruction_cleanup_root
|
||||
try:
|
||||
with install_lock(install_lock_path(install_dir)):
|
||||
if (install_dir / "UNSLOTH_PREBUILT_INFO.json").is_file():
|
||||
removed = remove_agent_instruction_files(cleanup_root)
|
||||
if removed:
|
||||
log(
|
||||
f"removed {removed} contributor-only agent instruction file(s) from install"
|
||||
)
|
||||
if install_dir.exists():
|
||||
log(
|
||||
f"existing llama.cpp install detected at {install_dir}; validating staged prebuilt update before replacement"
|
||||
|
|
@ -7168,14 +7222,16 @@ def main() -> int:
|
|||
# Install path only: route status logs to stdout (see _LOG_TO_STDOUT note).
|
||||
global _LOG_TO_STDOUT
|
||||
_LOG_TO_STDOUT = True
|
||||
install_arg = Path(args.install_dir).expanduser()
|
||||
install_prebuilt(
|
||||
install_dir = Path(args.install_dir).expanduser().resolve(),
|
||||
install_dir = install_arg.resolve(),
|
||||
llama_tag = args.llama_tag,
|
||||
published_repo = args.published_repo,
|
||||
published_release_tag = args.published_release_tag or "",
|
||||
override_has_rocm = args.has_rocm,
|
||||
override_rocm_gfx = args.rocm_gfx,
|
||||
force_cpu = args.cpu_fallback,
|
||||
instruction_cleanup_root = install_arg.absolute(),
|
||||
)
|
||||
return EXIT_SUCCESS
|
||||
|
||||
|
|
|
|||
|
|
@ -203,6 +203,31 @@ function New-UnslothTemporaryFile {
|
|||
return Get-Item -LiteralPath $tempPath
|
||||
}
|
||||
|
||||
function Remove-AgentInstructionFiles {
|
||||
param([string[]]$Roots)
|
||||
|
||||
foreach ($root in $Roots) {
|
||||
if (-not $root) { continue }
|
||||
$item = Get-Item -LiteralPath $root -Force -ErrorAction SilentlyContinue
|
||||
if (-not $item -or -not $item.PSIsContainer) { continue }
|
||||
if ($item.Attributes -band [System.IO.FileAttributes]::ReparsePoint) { continue }
|
||||
$pending = New-Object System.Collections.Stack
|
||||
$pending.Push($item)
|
||||
while ($pending.Count -gt 0) {
|
||||
$current = $pending.Pop()
|
||||
foreach ($child in @(Get-ChildItem -LiteralPath $current.FullName -Force -ErrorAction SilentlyContinue)) {
|
||||
if ($child.PSIsContainer) {
|
||||
if (-not ($child.Attributes -band [System.IO.FileAttributes]::ReparsePoint)) {
|
||||
$pending.Push($child)
|
||||
}
|
||||
} elseif ($child.Name -in @("AGENTS.md", "CLAUDE.md")) {
|
||||
Remove-Item -LiteralPath $child.FullName -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function Get-InstalledLlamaPrebuiltRelease {
|
||||
param([string]$InstallDir)
|
||||
|
||||
|
|
@ -2314,6 +2339,8 @@ if ((Test-Path $OxcValidatorDir) -and $NodeSource -ne "skip" -and (Get-Command n
|
|||
substep "OXC validator runtime skipped (no npm found); code validation degrades until Node is available" "Yellow"
|
||||
}
|
||||
|
||||
Remove-AgentInstructionFiles -Roots @($FrontendDir, $OxcValidatorDir)
|
||||
|
||||
# ==========================================================================
|
||||
# PHASE 3: Python environment + dependencies
|
||||
# ==========================================================================
|
||||
|
|
@ -3259,6 +3286,7 @@ if ($LocalLlamaCppSrc) {
|
|||
if ($LASTEXITCODE -ne 0) {
|
||||
substep "Could not create directory junction; copying instead..." "Yellow"
|
||||
Copy-Item -Recurse -LiteralPath $ResolvedLocal -Destination $LlamaCppDir
|
||||
Remove-AgentInstructionFiles -Roots @($LlamaCppDir)
|
||||
}
|
||||
Write-Host ""
|
||||
step "llama.cpp" "linked local directory: $ResolvedLocal"
|
||||
|
|
@ -4024,6 +4052,16 @@ if ($LocalLlamaCppLinked) {
|
|||
}
|
||||
}
|
||||
|
||||
$llamaCppItem = Get-Item -LiteralPath $LlamaCppDir -Force -ErrorAction SilentlyContinue
|
||||
$llamaCppIsLink = $llamaCppItem -and ($llamaCppItem.Attributes -band [System.IO.FileAttributes]::ReparsePoint)
|
||||
if (-not $llamaCppIsLink -and (
|
||||
-not $StudioHomeIsCustom -or
|
||||
(Test-Path -LiteralPath (Join-Path $LlamaCppDir $StudioOwnedMarker) -PathType Leaf) -or
|
||||
(Test-StudioOwnedAdoptable $LlamaCppDir)
|
||||
)) {
|
||||
Remove-AgentInstructionFiles -Roots @($LlamaCppDir)
|
||||
}
|
||||
|
||||
# ─────────────────────────────────────────────
|
||||
# Footer
|
||||
# ─────────────────────────────────────────────
|
||||
|
|
|
|||
|
|
@ -74,6 +74,16 @@ verbose_substep() {
|
|||
return 0
|
||||
}
|
||||
|
||||
_remove_agent_instruction_files() {
|
||||
local _root
|
||||
for _root in "$@"; do
|
||||
[ -d "$_root" ] || continue
|
||||
[ -L "$_root" ] && continue
|
||||
find "$_root" -type f \( -name 'AGENTS.md' -o -name 'CLAUDE.md' \) \
|
||||
-exec rm -f {} + 2>/dev/null || true
|
||||
done
|
||||
}
|
||||
|
||||
# ── Corporate-mirror / proxy escape hatch for the frontend npm/bun install (#6491) ──
|
||||
# studio/frontend/.npmrc pins registry=https://registry.npmjs.org/ as a supply-chain
|
||||
# lock. A project-level pin overrides a corporate user's ~/.npmrc proxy, so the install
|
||||
|
|
@ -847,6 +857,8 @@ elif [ -d "$_OXC_DIR" ] && [ "${NODE_SOURCE:-}" != skip ]; then
|
|||
substep "OXC validator runtime skipped (no npm found); code validation degrades until Node is available" "$C_WARN"
|
||||
fi
|
||||
|
||||
_remove_agent_instruction_files "$SCRIPT_DIR/frontend" "$_OXC_DIR"
|
||||
|
||||
# ── Python venv + deps ──
|
||||
|
||||
[ -d "$REPO_ROOT/.venv" ] && rm -rf "$REPO_ROOT/.venv"
|
||||
|
|
@ -1919,6 +1931,14 @@ if [ "$_LLAMA_CPP_DEGRADED" = true ] \
|
|||
fi
|
||||
fi
|
||||
|
||||
if [ ! -L "$LLAMA_CPP_DIR" ] && {
|
||||
[ "$_STUDIO_HOME_IS_CUSTOM" != true ] ||
|
||||
[ -f "$LLAMA_CPP_DIR/$_STUDIO_OWNED_MARKER" ] ||
|
||||
_studio_owned_adoptable "$LLAMA_CPP_DIR"
|
||||
}; then
|
||||
_remove_agent_instruction_files "$LLAMA_CPP_DIR"
|
||||
fi
|
||||
|
||||
# ── Footer ──
|
||||
if [ "$_LLAMA_ONLY" = "1" ]; then
|
||||
echo ""
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import importlib.util
|
|||
import io
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import tarfile
|
||||
import zipfile
|
||||
|
|
@ -30,6 +31,7 @@ AssetChoice = INSTALL_LLAMA_PREBUILT.AssetChoice
|
|||
ApprovedArtifactHash = INSTALL_LLAMA_PREBUILT.ApprovedArtifactHash
|
||||
ApprovedReleaseChecksums = INSTALL_LLAMA_PREBUILT.ApprovedReleaseChecksums
|
||||
hydrate_source_tree = INSTALL_LLAMA_PREBUILT.hydrate_source_tree
|
||||
remove_agent_instruction_files = INSTALL_LLAMA_PREBUILT.remove_agent_instruction_files
|
||||
validate_prebuilt_choice = INSTALL_LLAMA_PREBUILT.validate_prebuilt_choice
|
||||
activate_install_tree = INSTALL_LLAMA_PREBUILT.activate_install_tree
|
||||
activate_staged_dir = INSTALL_LLAMA_PREBUILT.activate_staged_dir
|
||||
|
|
@ -203,6 +205,178 @@ def test_extract_archive_rejects_zip_symlink_entry(tmp_path: Path):
|
|||
extract_archive(archive_path, tmp_path / "extract")
|
||||
|
||||
|
||||
def test_remove_agent_instruction_files_does_not_follow_links(tmp_path: Path):
|
||||
managed = tmp_path / "managed"
|
||||
nested = managed / "nested"
|
||||
external = tmp_path / "external"
|
||||
nested.mkdir(parents = True)
|
||||
external.mkdir()
|
||||
(managed / "AGENTS.md").write_text("managed root", encoding = "utf-8")
|
||||
(nested / "AGENTS.md").write_text("managed nested", encoding = "utf-8")
|
||||
(managed / "CLAUDE.md").write_text("managed Claude root", encoding = "utf-8")
|
||||
(nested / "CLAUDE.md").write_text("managed Claude nested", encoding = "utf-8")
|
||||
(external / "AGENTS.md").write_text("user owned", encoding = "utf-8")
|
||||
(external / "CLAUDE.md").write_text("user-owned Claude", encoding = "utf-8")
|
||||
try:
|
||||
(managed / "external-link").symlink_to(external, target_is_directory = True)
|
||||
linked_root = tmp_path / "linked-root"
|
||||
linked_root.symlink_to(external, target_is_directory = True)
|
||||
except OSError as exc:
|
||||
pytest.skip(f"directory symlinks unavailable: {exc}")
|
||||
|
||||
assert remove_agent_instruction_files(managed) == 4
|
||||
assert not list(managed.rglob("AGENTS.md"))
|
||||
assert not list(managed.rglob("CLAUDE.md"))
|
||||
assert (external / "AGENTS.md").read_text(encoding = "utf-8") == "user owned"
|
||||
assert (external / "CLAUDE.md").read_text(encoding = "utf-8") == "user-owned Claude"
|
||||
|
||||
assert remove_agent_instruction_files(linked_root) == 0
|
||||
assert (external / "AGENTS.md").exists()
|
||||
assert (external / "CLAUDE.md").exists()
|
||||
|
||||
|
||||
@pytest.mark.skipif(os.name != "nt", reason = "Windows junction behavior")
|
||||
def test_remove_agent_instruction_files_does_not_follow_windows_junctions(tmp_path: Path):
|
||||
managed = tmp_path / "managed"
|
||||
external = tmp_path / "external"
|
||||
managed.mkdir()
|
||||
external.mkdir()
|
||||
(external / "AGENTS.md").write_text("user owned", encoding = "utf-8")
|
||||
(external / "CLAUDE.md").write_text("user-owned Claude", encoding = "utf-8")
|
||||
|
||||
nested_junction = managed / "external-junction"
|
||||
root_junction = tmp_path / "linked-root"
|
||||
for junction in (nested_junction, root_junction):
|
||||
result = subprocess.run(
|
||||
["cmd", "/d", "/c", "mklink", "/J", str(junction), str(external)],
|
||||
capture_output = True,
|
||||
text = True,
|
||||
check = False,
|
||||
)
|
||||
if result.returncode != 0:
|
||||
pytest.skip(f"directory junctions unavailable: {result.stderr or result.stdout}")
|
||||
|
||||
assert remove_agent_instruction_files(managed) == 0
|
||||
assert remove_agent_instruction_files(root_junction) == 0
|
||||
assert (external / "AGENTS.md").read_text(encoding = "utf-8") == "user owned"
|
||||
assert (external / "CLAUDE.md").read_text(encoding = "utf-8") == "user-owned Claude"
|
||||
|
||||
|
||||
def test_remove_agent_instruction_files_prunes_linklike_directories(
|
||||
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
||||
):
|
||||
managed = tmp_path / "managed"
|
||||
simulated_junction = managed / "simulated-junction"
|
||||
simulated_junction.mkdir(parents = True)
|
||||
agents = simulated_junction / "AGENTS.md"
|
||||
claude = simulated_junction / "CLAUDE.md"
|
||||
agents.write_text("external instructions", encoding = "utf-8")
|
||||
claude.write_text("external Claude instructions", encoding = "utf-8")
|
||||
real_is_link_or_junction = INSTALL_LLAMA_PREBUILT._is_link_or_junction
|
||||
|
||||
monkeypatch.setattr(
|
||||
INSTALL_LLAMA_PREBUILT,
|
||||
"_is_link_or_junction",
|
||||
lambda path: path == simulated_junction or real_is_link_or_junction(path),
|
||||
)
|
||||
|
||||
assert remove_agent_instruction_files(managed) == 0
|
||||
assert agents.exists()
|
||||
assert claude.exists()
|
||||
|
||||
|
||||
def test_remove_agent_instruction_files_continues_after_unlink_error(
|
||||
tmp_path: Path, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
|
||||
):
|
||||
managed = tmp_path / "managed"
|
||||
managed.mkdir()
|
||||
blocked = managed / "AGENTS.md"
|
||||
removable = managed / "CLAUDE.md"
|
||||
blocked.write_text("blocked", encoding = "utf-8")
|
||||
removable.write_text("remove me", encoding = "utf-8")
|
||||
real_unlink = Path.unlink
|
||||
|
||||
def selective_unlink(path: Path, *args, **kwargs):
|
||||
if path == blocked:
|
||||
raise PermissionError(errno.EACCES, "Access is denied", str(path))
|
||||
return real_unlink(path, *args, **kwargs)
|
||||
|
||||
monkeypatch.setattr(Path, "unlink", selective_unlink)
|
||||
|
||||
assert remove_agent_instruction_files(managed) == 1
|
||||
assert blocked.exists()
|
||||
assert not removable.exists()
|
||||
captured = capsys.readouterr()
|
||||
assert "could not remove contributor-only instruction" in captured.out + captured.err
|
||||
|
||||
|
||||
def test_main_resolves_linked_install_path_and_preserves_cleanup_root(
|
||||
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
||||
):
|
||||
target = tmp_path / "target"
|
||||
linked_root = tmp_path / "linked-root"
|
||||
target.mkdir()
|
||||
try:
|
||||
linked_root.symlink_to(target, target_is_directory = True)
|
||||
except OSError as exc:
|
||||
pytest.skip(f"directory symlinks unavailable: {exc}")
|
||||
|
||||
received = {}
|
||||
monkeypatch.setattr(
|
||||
sys,
|
||||
"argv",
|
||||
["install_llama_prebuilt.py", "--install-dir", str(linked_root)],
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
INSTALL_LLAMA_PREBUILT,
|
||||
"install_prebuilt",
|
||||
lambda **kwargs: received.update(kwargs),
|
||||
)
|
||||
monkeypatch.setattr(INSTALL_LLAMA_PREBUILT, "_LOG_TO_STDOUT", False)
|
||||
|
||||
assert INSTALL_LLAMA_PREBUILT.main() == 0
|
||||
assert received["install_dir"] == target.resolve()
|
||||
assert received["instruction_cleanup_root"] == linked_root.absolute()
|
||||
assert received["instruction_cleanup_root"].is_symlink()
|
||||
|
||||
|
||||
def test_install_prebuilt_uses_explicit_instruction_cleanup_root(
|
||||
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
||||
):
|
||||
install_dir = tmp_path / "target"
|
||||
linked_root = tmp_path / "linked-root"
|
||||
install_dir.mkdir()
|
||||
(install_dir / "UNSLOTH_PREBUILT_INFO.json").write_text("{}", encoding = "utf-8")
|
||||
try:
|
||||
linked_root.symlink_to(install_dir, target_is_directory = True)
|
||||
except OSError as exc:
|
||||
pytest.skip(f"directory symlinks unavailable: {exc}")
|
||||
|
||||
cleanup_roots = []
|
||||
monkeypatch.setattr(INSTALL_LLAMA_PREBUILT, "detect_host", linux_host)
|
||||
monkeypatch.setattr(
|
||||
INSTALL_LLAMA_PREBUILT,
|
||||
"remove_agent_instruction_files",
|
||||
lambda root: cleanup_roots.append(root) or 0,
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
INSTALL_LLAMA_PREBUILT,
|
||||
"resolve_simple_install_release_plans",
|
||||
lambda *_args, **_kwargs: (_ for _ in ()).throw(RuntimeError("stop after cleanup")),
|
||||
)
|
||||
|
||||
with pytest.raises(RuntimeError, match = "stop after cleanup"):
|
||||
install_prebuilt(
|
||||
install_dir.resolve(),
|
||||
"latest",
|
||||
"unslothai/llama.cpp",
|
||||
"",
|
||||
instruction_cleanup_root = linked_root.absolute(),
|
||||
)
|
||||
|
||||
assert cleanup_roots == [linked_root.absolute()]
|
||||
|
||||
|
||||
def test_hydrate_source_tree_extracts_upstream_archive_contents(
|
||||
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
||||
):
|
||||
|
|
@ -224,6 +398,26 @@ def test_hydrate_source_tree_extracts_upstream_archive_contents(
|
|||
f"llama.cpp-{upstream_tag}/gguf-py/gguf/__init__.py",
|
||||
b"__all__ = []\n",
|
||||
)
|
||||
add_bytes_to_tar(
|
||||
archive,
|
||||
f"llama.cpp-{upstream_tag}/AGENTS.md",
|
||||
b"upstream contributor instructions\n",
|
||||
)
|
||||
add_bytes_to_tar(
|
||||
archive,
|
||||
f"llama.cpp-{upstream_tag}/examples/AGENTS.md",
|
||||
b"nested contributor instructions\n",
|
||||
)
|
||||
add_bytes_to_tar(
|
||||
archive,
|
||||
f"llama.cpp-{upstream_tag}/CLAUDE.md",
|
||||
b"Claude contributor instructions\n",
|
||||
)
|
||||
add_bytes_to_tar(
|
||||
archive,
|
||||
f"llama.cpp-{upstream_tag}/examples/CLAUDE.md",
|
||||
b"nested Claude contributor instructions\n",
|
||||
)
|
||||
|
||||
source_urls = set(INSTALL_LLAMA_PREBUILT.upstream_source_archive_urls(upstream_tag))
|
||||
|
||||
|
|
@ -244,6 +438,8 @@ def test_hydrate_source_tree_extracts_upstream_archive_contents(
|
|||
assert (install_dir / "convert_hf_to_gguf.py").exists()
|
||||
assert (install_dir / "gguf-py" / "gguf" / "__init__.py").exists()
|
||||
assert not (install_dir / f"llama.cpp-{upstream_tag}").exists()
|
||||
assert not list(install_dir.rglob("AGENTS.md"))
|
||||
assert not list(install_dir.rglob("CLAUDE.md"))
|
||||
|
||||
|
||||
def test_release_asset_download_url():
|
||||
|
|
@ -644,6 +840,29 @@ def test_activate_install_tree_restores_existing_install_after_activation_failur
|
|||
assert "restored previous install from rollback path" in output
|
||||
|
||||
|
||||
def test_activate_install_tree_preserves_symlink_to_resolved_target(
|
||||
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
||||
):
|
||||
install_dir = tmp_path / "target"
|
||||
linked_root = tmp_path / "linked-root"
|
||||
staging_dir = tmp_path / "staging"
|
||||
install_dir.mkdir()
|
||||
staging_dir.mkdir()
|
||||
(install_dir / "old.txt").write_text("old", encoding = "utf-8")
|
||||
(staging_dir / "new.txt").write_text("new", encoding = "utf-8")
|
||||
try:
|
||||
linked_root.symlink_to(install_dir, target_is_directory = True)
|
||||
except OSError as exc:
|
||||
pytest.skip(f"directory symlinks unavailable: {exc}")
|
||||
monkeypatch.setattr(INSTALL_LLAMA_PREBUILT, "confirm_install_tree", lambda *_args: None)
|
||||
|
||||
activate_install_tree(staging_dir, linked_root.resolve(), linux_host())
|
||||
|
||||
assert linked_root.is_symlink()
|
||||
assert (linked_root / "new.txt").read_text(encoding = "utf-8") == "new"
|
||||
assert not (linked_root / "old.txt").exists()
|
||||
|
||||
|
||||
def test_activate_install_tree_cleans_all_paths_when_rollback_restore_fails(
|
||||
tmp_path: Path, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
|
||||
):
|
||||
|
|
@ -2007,6 +2226,14 @@ def test_install_prebuilt_skips_download_when_existing_install_matches(
|
|||
approved_checksums = checksums,
|
||||
prebuilt_fallback_used = False,
|
||||
)
|
||||
(install_dir / "AGENTS.md").write_text("old root instructions", encoding = "utf-8")
|
||||
nested_agents = install_dir / "examples" / "AGENTS.md"
|
||||
nested_agents.parent.mkdir()
|
||||
nested_agents.write_text("old nested instructions", encoding = "utf-8")
|
||||
(install_dir / "CLAUDE.md").write_text("old Claude instructions", encoding = "utf-8")
|
||||
(nested_agents.parent / "CLAUDE.md").write_text(
|
||||
"old nested Claude instructions", encoding = "utf-8"
|
||||
)
|
||||
|
||||
monkeypatch.setattr(INSTALL_LLAMA_PREBUILT, "detect_host", lambda: host)
|
||||
monkeypatch.setattr(
|
||||
|
|
@ -2026,6 +2253,35 @@ def test_install_prebuilt_skips_download_when_existing_install_matches(
|
|||
)
|
||||
|
||||
install_prebuilt(install_dir, "latest", "unslothai/llama.cpp", "")
|
||||
assert not list(install_dir.rglob("AGENTS.md"))
|
||||
assert not list(install_dir.rglob("CLAUDE.md"))
|
||||
|
||||
|
||||
def test_setup_scripts_prune_agent_files_without_shipping_a_repo_copy():
|
||||
setup_sh = (PACKAGE_ROOT / "studio" / "setup.sh").read_text(encoding = "utf-8")
|
||||
setup_ps1 = (PACKAGE_ROOT / "studio" / "setup.ps1").read_text(encoding = "utf-8")
|
||||
|
||||
assert '_remove_agent_instruction_files "$SCRIPT_DIR/frontend" "$_OXC_DIR"' in setup_sh
|
||||
assert '_remove_agent_instruction_files "$LLAMA_CPP_DIR"' in setup_sh
|
||||
assert "-name 'CLAUDE.md'" in setup_sh
|
||||
assert 'if [ ! -L "$LLAMA_CPP_DIR" ] && {' in setup_sh
|
||||
assert '${_LOCAL_LLAMA_CPP_LINKED:-false}" != true' not in setup_sh
|
||||
assert "$LLAMA_CPP_DIR/$_STUDIO_OWNED_MARKER" in setup_sh
|
||||
assert '_studio_owned_adoptable "$LLAMA_CPP_DIR"' in setup_sh
|
||||
assert "Remove-AgentInstructionFiles -Roots @($FrontendDir, $OxcValidatorDir)" in setup_ps1
|
||||
assert '"CLAUDE.md"' in setup_ps1
|
||||
assert '-Include "AGENTS.md", "CLAUDE.md"' not in setup_ps1
|
||||
assert '$child.Name -in @("AGENTS.md", "CLAUDE.md")' in setup_ps1
|
||||
assert "$llamaCppIsLink" in setup_ps1
|
||||
assert "if (-not $LocalLlamaCppLinked)" not in setup_ps1
|
||||
assert "Join-Path $LlamaCppDir $StudioOwnedMarker" in setup_ps1
|
||||
assert "Test-StudioOwnedAdoptable $LlamaCppDir" in setup_ps1
|
||||
assert (
|
||||
"Copy-Item -Recurse -LiteralPath $ResolvedLocal -Destination $LlamaCppDir\n"
|
||||
" Remove-AgentInstructionFiles -Roots @($LlamaCppDir)"
|
||||
) in setup_ps1
|
||||
assert not (PACKAGE_ROOT / "studio" / "frontend" / "src" / "i18n" / "AGENTS.md").exists()
|
||||
assert (PACKAGE_ROOT / "studio" / "frontend" / "src" / "i18n" / "README.md").is_file()
|
||||
|
||||
|
||||
def test_install_prebuilt_does_not_skip_unhealthy_existing_install(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue