Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion archdev/references/bootstrap.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,11 @@ scope. Do not edit agent instruction files or commit changes without permission.
The Bash and PowerShell bootstrap scripts resolve or install the executable
and check its capabilities. They do not install or refresh hooks, install
skills, authenticate, or create repository configuration. Do not run them to
bypass the guide's consent steps.
bypass the guide's consent steps. When the executable is Homebrew-managed
(linked from a Homebrew Cellar), they update it with
`brew upgrade archastro/tools/archdev` (or `brew reinstall` for a current but
broken copy) and never install a second copy; without a working `brew` they
stop with an error naming that binary.

## Version and login

Expand Down
119 changes: 112 additions & 7 deletions archdev/scripts/bootstrap.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,6 @@ function Install-ArchDev {
return (Resolve-ArchDevPath (Join-Path $installDir "archdev.exe"))
}

$existing = Get-Command archdev -ErrorAction SilentlyContinue
$archdev = if ($existing) { Resolve-ArchDevPath $existing.Source } else { Install-ArchDev }

$minVersion = [Version]"0.49.6"

function Test-Version([string]$Binary) {
Expand Down Expand Up @@ -60,17 +57,125 @@ function Test-Skill([string]$Binary) {
return ($LASTEXITCODE -eq 0 -and (($helpText -join "`n") -match "--local"))
}

if (-not (Test-Skill $archdev)) {
[Console]::Error.WriteLine("Updating ArchDev: this skill requires 0.49.6+ and repository hook setup with --local.")
$archdev = Install-ArchDev
# A Homebrew install owns its copy. Installing another one elsewhere leaves two
# binaries that are upgraded separately, and whichever comes first on PATH wins.
# Homebrew exists only on macOS and Linux; Windows keeps the installer path.
$brewFormula = "archastro/tools/archdev"

# Resolves every symlink in the path, including directory links such as
# Homebrew's opt link. A dangling keg link still yields its Cellar path.
function Get-RealPath([string]$Path) {
$pending = [Collections.Generic.List[string]]::new()
$pending.AddRange([string[]]@([IO.Path]::GetFullPath($Path).Split("/") | Where-Object { $_ }))
$real = ""
$hops = 0
while ($pending.Count -gt 0) {
$part = $pending[0]
$pending.RemoveAt(0)
if ($part -eq ".") { continue }
if ($part -eq "..") {
$real = $real.Substring(0, [Math]::Max(0, $real.LastIndexOf("/")))
continue
}
$next = "$real/$part"
$item = Get-Item -LiteralPath $next -Force -ErrorAction SilentlyContinue
if ($item -and $item.LinkTarget -and $hops -lt 32) {
$hops++
if ($item.LinkTarget.StartsWith("/")) { $real = "" }
$pending.InsertRange(0, [string[]]@($item.LinkTarget.Split("/") | Where-Object { $_ }))
continue
}
$real = $next
}
return $real
}

# Test-Path accepts a dangling link; a removed keg leaves one behind.
function Test-LinkedFile([string]$Path) {
return (Test-Path -LiteralPath (Get-RealPath $Path) -PathType Leaf)
}

function Test-HomebrewArchDev([string]$Path) {
return ((Get-RealPath $Path) -like "*/Cellar/archdev/*")
}

function Get-Brew {
return (Get-Command brew -CommandType Application -ErrorAction SilentlyContinue | Select-Object -First 1)
}

# The Homebrew-managed archdev on PATH, else <brew --prefix>/bin/archdev when
# Homebrew links it there, even if another copy comes first on PATH.
function Find-HomebrewArchDev($Candidate) {
if ($env:OS -eq "Windows_NT") { return $null }
if ($Candidate -and (Test-HomebrewArchDev $Candidate)) { return $Candidate }
$brew = Get-Brew
if (-not $brew) { return $null }
$prefix = @(& $brew.Source --prefix 2>$null) | Select-Object -First 1
if ($LASTEXITCODE -ne 0 -or -not $prefix) { return $null }
$linked = Join-Path $prefix "bin/archdev"
if ((Get-Item -LiteralPath $linked -Force -ErrorAction SilentlyContinue) -and (Test-HomebrewArchDev $linked)) {
return $linked
}
return $null
}

# Repairs a Homebrew-managed archdev with Homebrew rather than a second copy:
# upgrade when it reports a too-old version; reinstall when the binary is
# missing, crashes, or a current version fails the capability probes, where
# upgrade would be a no-op.
function Update-HomebrewArchDev([string]$Binary) {
# The brew that owns this keg; another prefix's brew could install a second copy.
$real = Get-RealPath $Binary
$brew = $real.Substring(0, $real.IndexOf("/Cellar/archdev/")) + "/bin/brew"
if (-not (Test-Path -LiteralPath $brew -PathType Leaf)) {
throw "ArchDev at $Binary is managed by Homebrew, but $brew is missing. Run: brew upgrade $brewFormula. Not installing a second copy of ArchDev."
}
$version = $null
if (Test-LinkedFile $Binary) {
$raw = (& $Binary --version 2>$null | Select-Object -First 1) -replace "[^0-9.]", ""
try { $version = [Version]$raw } catch { $version = $null }
}
# Only a readable, too-old version needs an upgrade; anything else is broken.
$action = if ($version -and $version -lt $minVersion) { "upgrade" } else { "reinstall" }
[Console]::Error.WriteLine("Updating Homebrew-managed ArchDev at ${Binary}: brew $action $brewFormula")
# Callers read stdout as the archdev path, so brew's progress goes to stderr.
& $brew $action $brewFormula | ForEach-Object { [Console]::Error.WriteLine($_) }
if ($LASTEXITCODE -ne 0) {
throw "brew $action $brewFormula failed. ArchDev at $Binary is managed by Homebrew; not installing a second copy."
}
}

$existing = Get-Command archdev -ErrorAction SilentlyContinue
$candidate = if ($existing) { Resolve-ArchDevPath $existing.Source } else { $null }
$homebrewArchDev = Find-HomebrewArchDev $candidate
if ($homebrewArchDev) {
$archdev = $homebrewArchDev
if ($candidate -and $candidate -ne $archdev) {
[Console]::Error.WriteLine("Using Homebrew-managed ArchDev at $archdev; $candidate comes first on PATH.")
}
if (-not (Test-LinkedFile $archdev) -or -not (Test-Skill $archdev)) {
Update-HomebrewArchDev $archdev
}
} else {
$archdev = if ($candidate) { $candidate } else { Install-ArchDev }
if (-not (Test-Skill $archdev)) {
[Console]::Error.WriteLine("Updating ArchDev: this skill requires 0.49.6+ and repository hook setup with --local.")
$archdev = Install-ArchDev
}
}

if ($homebrewArchDev -and -not (Test-LinkedFile $archdev)) {
throw "Homebrew did not leave an executable at $archdev; not installing a second copy."
}
if (-not (Test-Path -LiteralPath $archdev -PathType Leaf)) {
throw "ArchDev installer did not create an executable at $archdev"
}
& $archdev --version *> $null
if ($LASTEXITCODE -ne 0) { throw "ArchDev version verification failed" }
if (-not (Test-Skill $archdev)) { throw "Installed ArchDev lacks required commands or --local hook setup (need 0.49.6+); stopping without a global fallback" }
if (-not (Test-Skill $archdev)) {
if ($homebrewArchDev) { throw "Homebrew-managed ArchDev at $archdev still fails the capability check after brew; not installing a second copy." }
throw "Installed ArchDev lacks required commands or --local hook setup (need 0.49.6+); stopping without a global fallback"
}

# Resolving the executable must not choose configuration scope. Install and
# repair hooks only through the approved branch in https://archdev.ai/install.md.
Expand Down
121 changes: 108 additions & 13 deletions archdev/scripts/bootstrap.sh
Original file line number Diff line number Diff line change
Expand Up @@ -59,14 +59,6 @@ install_archdev() (
fi
)

candidate="$(command -v archdev 2>/dev/null || true)"
if [[ -n "$candidate" ]]; then
executable="$(absolute_path "$candidate")"
else
install_archdev || exit 1
executable="$(absolute_path "$install_dir/archdev")"
fi

version_ok() {
local raw version
raw="$("$1" --version 2>/dev/null | head -n 1)"
Expand All @@ -85,19 +77,122 @@ supports_skill() {
"$1" repo hook setup --help 2>/dev/null | grep -F -- "--local" >/dev/null
}

if ! supports_skill "$executable"; then
printf 'Updating ArchDev: this skill requires 0.49.6+ and repository hook setup with --local.\n' >&2
install_archdev || exit 1
executable="$(absolute_path "$install_dir/archdev")"
# A Homebrew install owns its copy. Installing another one elsewhere leaves two
# binaries that are upgraded separately, and whichever comes first on PATH wins.
brew_formula="archastro/tools/archdev"

# Follows symlinks one hop at a time; macOS before 12.3 has no readlink -f.
real_path() {
local path="$1" link hops=0 directory
while [[ -L "$path" && "$hops" -lt 32 ]]; do
link="$(readlink "$path")" || break
case "$link" in
/*) path="$link" ;;
*) path="$(dirname "$path")/$link" ;;
esac
hops=$((hops + 1))
done
# A dangling keg link still names its Cellar path.
directory="$(cd -P "$(dirname "$path")" 2>/dev/null && pwd)" || {
printf '%s\n' "$path"
return 0
}
printf '%s/%s\n' "$directory" "$(basename "$path")"
}

is_homebrew_archdev() {
case "$(real_path "$1")" in
*/Cellar/archdev/*) return 0 ;;
esac
return 1
}

# The Homebrew-managed archdev on PATH, else <brew --prefix>/bin/archdev when
# Homebrew links it there, even if another copy comes first on PATH.
find_homebrew_archdev() {
local candidate="$1" prefix
if [[ -n "$candidate" ]] && is_homebrew_archdev "$candidate"; then
# Keep the link path: an upgrade removes the versioned keg behind it.
printf '%s/%s\n' "$(cd "$(dirname "$candidate")" && pwd -L)" "$(basename "$candidate")"
return 0
fi
command -v brew >/dev/null 2>&1 || return 1
prefix="$(brew --prefix 2>/dev/null)" || return 1
[[ -n "$prefix" ]] || return 1
if [[ -e "$prefix/bin/archdev" || -L "$prefix/bin/archdev" ]] &&
is_homebrew_archdev "$prefix/bin/archdev"; then
printf '%s/bin/archdev\n' "$prefix"
return 0
fi
return 1
}

# Repairs a Homebrew-managed archdev with Homebrew rather than a second copy:
# upgrade when it reports a too-old version; reinstall when the binary is
# missing, crashes, or a current version fails the capability probes, where
# upgrade would be a no-op.
update_homebrew_archdev() {
local binary="$1" action=reinstall brew version=""
# The brew that owns this keg; another prefix's brew could install a second copy.
brew="$(real_path "$binary")"
brew="${brew%%/Cellar/archdev/*}/bin/brew"
if [[ ! -x "$brew" ]]; then
printf 'ArchDev at %s is managed by Homebrew, but %s is missing. Run: brew upgrade %s\n' "$binary" "$brew" "$brew_formula" >&2
printf 'Not installing a second copy of ArchDev.\n' >&2
return 1
fi
if [[ -x "$binary" ]]; then
version="$("$binary" --version 2>/dev/null | head -n 1 | grep -Eo '[0-9]+\.[0-9]+\.[0-9]+' | head -n 1 || true)"
fi
# Only a readable, too-old version needs an upgrade; anything else is broken.
if [[ -n "$version" ]] && ! version_ok "$binary"; then
action=upgrade
fi
printf 'Updating Homebrew-managed ArchDev at %s: brew %s %s\n' "$binary" "$action" "$brew_formula" >&2
if ! "$brew" "$action" "$brew_formula" >&2; then
printf 'brew %s %s failed. ArchDev at %s is managed by Homebrew; not installing a second copy.\n' "$action" "$brew_formula" "$binary" >&2
return 1
fi
}

candidate="$(command -v archdev 2>/dev/null || true)"
homebrew_archdev="$(find_homebrew_archdev "$candidate" || true)"
if [[ -n "$homebrew_archdev" ]]; then
executable="$homebrew_archdev"
if [[ -n "$candidate" && "$(absolute_path "$candidate")" != "$executable" ]]; then
printf 'Using Homebrew-managed ArchDev at %s; %s comes first on PATH.\n' "$executable" "$(absolute_path "$candidate")" >&2
fi
if ! supports_skill "$executable"; then
update_homebrew_archdev "$executable" || exit 1
fi
else
if [[ -n "$candidate" ]]; then
executable="$(absolute_path "$candidate")"
else
install_archdev || exit 1
executable="$(absolute_path "$install_dir/archdev")"
fi
if ! supports_skill "$executable"; then
printf 'Updating ArchDev: this skill requires 0.49.6+ and repository hook setup with --local.\n' >&2
install_archdev || exit 1
executable="$(absolute_path "$install_dir/archdev")"
fi
fi

[[ -x "$executable" ]] || {
printf 'ArchDev installer did not create an executable at %s\n' "$executable" >&2
if [[ -n "$homebrew_archdev" ]]; then
printf 'Homebrew did not leave an executable at %s; not installing a second copy.\n' "$executable" >&2
else
printf 'ArchDev installer did not create an executable at %s\n' "$executable" >&2
fi
exit 1
}

"$executable" --version >&2
supports_skill "$executable" || {
if [[ -n "$homebrew_archdev" ]]; then
printf 'Homebrew-managed ArchDev at %s still fails the capability check after brew; not installing a second copy.\n' "$executable" >&2
fi
printf 'Installed ArchDev lacks required commands or --local hook setup (need 0.49.6+); stopping without a global fallback.\n' >&2
exit 1
}
Expand Down
6 changes: 5 additions & 1 deletion plugins/antigravity/skills/archdev/references/bootstrap.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,11 @@ scope. Do not edit agent instruction files or commit changes without permission.
The Bash and PowerShell bootstrap scripts resolve or install the executable
and check its capabilities. They do not install or refresh hooks, install
skills, authenticate, or create repository configuration. Do not run them to
bypass the guide's consent steps.
bypass the guide's consent steps. When the executable is Homebrew-managed
(linked from a Homebrew Cellar), they update it with
`brew upgrade archastro/tools/archdev` (or `brew reinstall` for a current but
broken copy) and never install a second copy; without a working `brew` they
stop with an error naming that binary.

## Version and login

Expand Down
Loading
Loading