Files
boshu2__agentops/scripts/install-codex.ps1
T
Bo e431339c40 feat(hookless)!: remove all hooks — skills + CLI only (soc-57b7f #remove-all-hooks) (#511)
## What

AgentOps 3.0 goes **fully hookless**. This removes the entire hook
product surface — the product is now skills + the `ao` CLI with **zero
hooks**. CI is the authoritative gate.

## Deleted

- `hooks/` — 46 hook scripts + `hooks.json` + examples (whole directory)
- `cli/embedded/hooks/` + embedded `hooks.json` +
`embedded/lib/hook-helpers.sh`
- `lib/hook-helpers.sh`
- `ao hooks` CLI command: `cli/cmd/ao/hooks.go`, `hooks_run.go` + 5 hook
test files; `cli/internal/bridge/hooks.go`;
`cli/internal/doctor/fix_hooks.go` + test
- Codex hook manifest (`hooks/codex-hooks.json`) + handler/audit scripts
(`scripts/audit-codex-hooks.sh`, `install-dev-hooks.sh`,
`test-hooks-output.sh`, `test-codex-hookless-lifecycle.sh`,
`test-hookless-rpi-phased.sh`)
- Hook lease surface: `docs/contracts/hook-lease-*`,
`schemas/hook-lease.v1.schema.json`,
`scripts/check-hook-lease-inventory.sh`,
`check-hook-port-replacements.sh`, `generate-hook-lease-inventory.py`
- Hook docs: `docs/HOOKS.md`, `cli/docs/HOOKS.md`,
`docs/architecture/hook-noise-audit.md`,
`docs/contracts/hook-runtime-contract.md`
- `tests/hooks/` (whole dir) + hook test scripts; 5 hook eval suites +
fixtures

## CI gates removed

`hook-preflight`, `validate-hooks-doc-parity`, `hook-output-schema-lint`
(+ scripts/bats), the hook eval suites, the `test-runtime-codex-smoke` /
`test-codex-native-install` / `test-codex-plugin-install` hook-handler
assertions, the cli-integration hook-lifecycle steps, the bats
`tests/hooks/*` glob + orphan-hooks audit, and the `--with-hooks`
install path. `embedded-sync` now validates only lib/skills. Both
summary `needs:` lists + the summary echo +
`docs/contracts/ci-jobs.yaml` updated; `validate-ci-policy-parity`
passes (66 rows). `.codex-plugin/plugin.json` drops the "Hooks"
capability.

## Load-bearing PRESERVED (reported, not forced)

- **`cli/cmd/ao/codex_runtime.go`** — the codex runtime *lifecycle
profile* (`HookCapable`/`HookConfigured` detection) is a
runtime-capability abstraction used by 6 `validate-codex-*` CI gates,
distinct from the hook product. Deleting it is a separate refactor.
- **`hooks-authoring` skill** + `schemas/hooks-manifest.v1.schema.json`
— reframed as an opt-in "author your own hooks" guide. Deleting the
skill cascades into catalog/domain-map/dispositions/context-map golden
files (skill-count sync), out of scope for the hook-product removal.
- **`scripts/pre-push-gate.sh`** — retired local gate (not in CI);
self-skips deleted hook scripts via `[[ -x ]]` guards; its 55 bats tests
still pass.
- Domain-enforcement audit keeps the `enforced`-mode schema for
backward-compatible JSON readers, but the hookless resolver only returns
`audited`/`unavailable`.

## Verification

`go build` / `go vet` clean; `go test ./...` = **11849 passed, 0
failed** (68 pkgs). gofmt clean on all touched files. Green: registry
`--check` (0 hooks), `regen-codex-hashes --check`,
`validate-ci-policy-parity`, `check-wiring-closure`,
`validate-embedded-sync`, `validate-manifests`, `validate-agents-split`,
`doc-release` (0 broken links), `generate-cli-reference --check`,
`heal.sh --strict`, codex/claude/plugin smoke tests, baseline-audit (0
stale). Diff: 231 files, +354 / −39803.

Closes-scenario: soc-57b7f#remove-all-hooks
Bounded-context: BC5-Runtime
Evidence: .github/workflows/validate.yml
2026-05-24 11:19:37 -04:00

355 lines
12 KiB
PowerShell

# install-codex.ps1 - Install AgentOps into the local Codex native plugin cache on Windows.
#
# Usage:
# powershell -ExecutionPolicy Bypass -File .\scripts\install-codex.ps1
# irm https://raw.githubusercontent.com/boshu2/agentops/main/scripts/install-codex.ps1 | iex
[CmdletBinding()]
param(
[string]$RepoRoot = $env:AGENTOPS_BUNDLE_ROOT,
[string]$CodexHome = $env:CODEX_HOME,
[string]$Version = $env:AGENTOPS_INSTALL_REF,
[string]$UpdateCommand = "irm https://raw.githubusercontent.com/boshu2/agentops/main/scripts/install-codex.ps1 | iex"
)
$ErrorActionPreference = "Stop"
$PluginName = "agentops"
$MarketplaceName = "agentops-marketplace"
$PluginKey = "$PluginName@$MarketplaceName"
$InstallRef = if ([string]::IsNullOrWhiteSpace($Version)) { "main" } else { $Version }
$ArchiveUrl = if ($InstallRef -eq "main") {
"https://codeload.github.com/boshu2/agentops/tar.gz/refs/heads/main"
} else {
"https://codeload.github.com/boshu2/agentops/tar.gz/refs/tags/$InstallRef"
}
function Write-Info {
param([string]$Message)
Write-Host "[ok] $Message" -ForegroundColor Green
}
function Write-Warn {
param([string]$Message)
Write-Host "[warn] $Message" -ForegroundColor Yellow
}
function Fail {
param([string]$Message)
throw $Message
}
function Require-Path {
param([string]$Path, [string]$Label)
if (-not (Test-Path -LiteralPath $Path)) {
Fail "Missing ${Label}: $Path"
}
}
function Get-AgentOpsTempDir {
$path = Join-Path ([System.IO.Path]::GetTempPath()) ("agentops-codex-" + [Guid]::NewGuid().ToString("N"))
New-Item -ItemType Directory -Path $path | Out-Null
return $path
}
function Resolve-ArchiveRoot {
param([string]$ArchiveFile)
$entries = @(& tar.exe -tzf $ArchiveFile)
if ($LASTEXITCODE -ne 0 -or $entries.Count -eq 0 -or [string]::IsNullOrWhiteSpace($entries[0])) {
Fail "Could not read AgentOps archive"
}
return ($entries[0] -split "/")[0]
}
function Get-BashFlavor {
if (-not (Get-Command bash -ErrorAction SilentlyContinue)) {
return $null
}
try {
return ((& bash -lc "uname -s" 2>$null) | Select-Object -First 1)
} catch {
return $null
}
}
function Convert-ToBashPath {
param([string]$Path, [string]$BashFlavor)
$fullPath = [System.IO.Path]::GetFullPath($Path)
if ($fullPath -match "^([A-Za-z]):\\(.*)$") {
$drive = $matches[1].ToLowerInvariant()
$tail = $matches[2] -replace "\\", "/"
if ($BashFlavor -like "Linux*") {
return "/mnt/$drive/$tail"
}
return "/$drive/$tail"
}
return ($fullPath -replace "\\", "/")
}
function Copy-CleanDirectory {
param([string]$Source, [string]$Destination)
if (Test-Path -LiteralPath $Destination) {
Remove-Item -LiteralPath $Destination -Recurse -Force
}
New-Item -ItemType Directory -Path (Split-Path -Parent $Destination) -Force | Out-Null
Copy-Item -LiteralPath $Source -Destination $Destination -Recurse -Force
}
function Upsert-TomlKey {
param([string]$File, [string]$Section, [string]$Key, [string]$Value)
New-Item -ItemType Directory -Path (Split-Path -Parent $File) -Force | Out-Null
if (-not (Test-Path -LiteralPath $File)) {
Set-Content -LiteralPath $File -Value @($Section, "$Key = $Value") -Encoding utf8
return
}
$lines = [System.Collections.Generic.List[string]]::new()
$lines.AddRange([string[]](Get-Content -LiteralPath $File))
$sectionIndex = -1
for ($i = 0; $i -lt $lines.Count; $i++) {
if ($lines[$i] -eq $Section) {
$sectionIndex = $i
break
}
}
if ($sectionIndex -lt 0) {
if ($lines.Count -gt 0 -and $lines[$lines.Count - 1] -ne "") {
$lines.Add("")
}
$lines.Add($Section)
$lines.Add("$Key = $Value")
Set-Content -LiteralPath $File -Value $lines -Encoding utf8
return
}
$insertIndex = $lines.Count
for ($i = $sectionIndex + 1; $i -lt $lines.Count; $i++) {
if ($lines[$i] -match "^\[") {
$insertIndex = $i
break
}
if ($lines[$i] -match "^\s*$([regex]::Escape($Key))\s*=") {
$lines[$i] = "$Key = $Value"
Set-Content -LiteralPath $File -Value $lines -Encoding utf8
return
}
}
$lines.Insert($insertIndex, "$Key = $Value")
Set-Content -LiteralPath $File -Value $lines -Encoding utf8
}
function Remove-TomlKey {
param([string]$File, [string]$Section, [string]$Key)
if (-not (Test-Path -LiteralPath $File)) {
return
}
$lines = [System.Collections.Generic.List[string]]::new()
$lines.AddRange([string[]](Get-Content -LiteralPath $File))
$sectionIndex = -1
for ($i = 0; $i -lt $lines.Count; $i++) {
if ($lines[$i] -eq $Section) {
$sectionIndex = $i
break
}
}
if ($sectionIndex -lt 0) {
return
}
for ($i = $sectionIndex + 1; $i -lt $lines.Count; $i++) {
if ($lines[$i] -match "^\[") {
break
}
if ($lines[$i] -match "^\s*$([regex]::Escape($Key))\s*=") {
$lines.RemoveAt($i)
$i--
}
}
Set-Content -LiteralPath $File -Value $lines -Encoding utf8
}
function Archive-SkillRoot {
param([string]$Root, [string]$SkillsSource, [bool]$ManagedRoot)
if (-not (Test-Path -LiteralPath $Root)) {
return $null
}
$timestamp = Get-Date -Format "yyyyMMdd-HHmmss"
$backupDir = Join-Path (Split-Path -Parent $Root) ("skills.backup.$timestamp")
$moved = 0
foreach ($skill in Get-ChildItem -LiteralPath $SkillsSource -Directory) {
$target = Join-Path $Root $skill.Name
if (-not (Test-Path -LiteralPath $target)) {
continue
}
if (-not $ManagedRoot -and -not (Test-Path -LiteralPath (Join-Path $target ".agentops-generated.json"))) {
continue
}
New-Item -ItemType Directory -Path $backupDir -Force | Out-Null
Move-Item -LiteralPath $target -Destination (Join-Path $backupDir $skill.Name) -Force
$moved += 1
}
foreach ($name in @(".agentops-manifest.json", ".agentops-codex-state.json")) {
$path = Join-Path $Root $name
if (Test-Path -LiteralPath $path) {
New-Item -ItemType Directory -Path $backupDir -Force | Out-Null
Move-Item -LiteralPath $path -Destination (Join-Path $backupDir $name) -Force
$moved += 1
}
}
if ($moved -gt 0) {
return $backupDir
}
return $null
}
$tempDir = $null
try {
Write-Host "Installing AgentOps for Codex on Windows..."
Write-Host ""
if ([string]::IsNullOrWhiteSpace($CodexHome)) {
$CodexHome = Join-Path $HOME ".codex"
}
$CodexHome = [System.IO.Path]::GetFullPath($CodexHome)
if ([string]::IsNullOrWhiteSpace($RepoRoot)) {
if (-not (Get-Command tar.exe -ErrorAction SilentlyContinue)) {
Fail "Missing required command: tar.exe"
}
$tempDir = Get-AgentOpsTempDir
$archiveFile = Join-Path $tempDir "agentops.tar.gz"
Write-Info "Downloading AgentOps bundle..."
Invoke-WebRequest -UseBasicParsing -Uri $ArchiveUrl -OutFile $archiveFile
$archiveRoot = Resolve-ArchiveRoot -ArchiveFile $archiveFile
& tar.exe -xzf $archiveFile -C $tempDir
if ($LASTEXITCODE -ne 0) {
Fail "Could not extract AgentOps archive"
}
$RepoRoot = Join-Path $tempDir $archiveRoot
}
$RepoRoot = [System.IO.Path]::GetFullPath($RepoRoot)
$pluginManifest = Join-Path $RepoRoot ".codex-plugin\plugin.json"
$marketplaceFile = Join-Path $RepoRoot "plugins\marketplace.json"
$skillsSrc = Join-Path $RepoRoot "skills-codex"
$skillManifest = Join-Path $skillsSrc ".agentops-manifest.json"
$pluginCacheRoot = Join-Path $CodexHome "plugins\cache\$MarketplaceName\$PluginName\local"
$configFile = Join-Path $CodexHome "config.toml"
$installMeta = Join-Path $CodexHome ".agentops-codex-install.json"
$pluginStateFile = Join-Path $pluginCacheRoot ".agentops-codex-state.json"
Require-Path $pluginManifest "Codex plugin manifest"
Require-Path $marketplaceFile "Codex marketplace manifest"
Require-Path $skillsSrc "Codex-native skill bundle"
Require-Path $skillManifest "Codex skill manifest"
Write-Info "Installing AgentOps Codex native plugin..."
if (Test-Path -LiteralPath $pluginCacheRoot) {
Remove-Item -LiteralPath $pluginCacheRoot -Recurse -Force
}
New-Item -ItemType Directory -Path $pluginCacheRoot -Force | Out-Null
Copy-Item -LiteralPath (Join-Path $RepoRoot ".codex-plugin") -Destination (Join-Path $pluginCacheRoot ".codex-plugin") -Recurse -Force
Copy-Item -LiteralPath $skillsSrc -Destination (Join-Path $pluginCacheRoot "skills-codex") -Recurse -Force
foreach ($optionalFile in @(".mcp.json", ".app.json")) {
$path = Join-Path $RepoRoot $optionalFile
if (Test-Path -LiteralPath $path) {
Copy-Item -LiteralPath $path -Destination (Join-Path $pluginCacheRoot $optionalFile) -Force
}
}
Upsert-TomlKey $configFile "[features]" "plugins" "true"
Upsert-TomlKey $configFile "[plugins.`"$PluginKey`"]" "enabled" "true"
Upsert-TomlKey $configFile "[ui]" "suppress_unstable_features_warning" "true"
Remove-TomlKey $configFile "[features]" "codex_hooks"
$manifestHash = (Get-FileHash -LiteralPath $skillManifest -Algorithm SHA256).Hash.ToLowerInvariant()
$installedManifest = Join-Path $pluginCacheRoot "skills-codex\.agentops-manifest.json"
$installedHash = (Get-FileHash -LiteralPath $installedManifest -Algorithm SHA256).Hash.ToLowerInvariant()
if ($manifestHash -ne $installedHash) {
Fail "Installed plugin cache manifest hash mismatch; expected $manifestHash, got $installedHash"
}
$skillCount = @(Get-ChildItem -LiteralPath (Join-Path $pluginCacheRoot "skills-codex") -Directory | Where-Object {
Test-Path -LiteralPath (Join-Path $_.FullName "SKILL.md")
}).Count
$legacyBackup = Archive-SkillRoot -Root (Join-Path $CodexHome "skills") -SkillsSource $skillsSrc -ManagedRoot $true
$userSkillsRoot = Join-Path (Split-Path -Parent $CodexHome) ".agents\skills"
$managedUserRoot = (Test-Path -LiteralPath (Join-Path $userSkillsRoot ".agentops-manifest.json")) -or (Test-Path -LiteralPath (Join-Path $userSkillsRoot ".agentops-codex-state.json"))
$userBackup = Archive-SkillRoot -Root $userSkillsRoot -SkillsSource $skillsSrc -ManagedRoot $managedUserRoot
$installedAt = (Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")
$hookRuntime = "hookless-default"
$state = [ordered]@{
installed_at = $installedAt
install_mode = "native-plugin"
hook_runtime = $hookRuntime
hooks_installed = $false
version = $InstallRef
manifest_hash = $manifestHash
skill_count = $skillCount
plugin_root = $pluginCacheRoot
}
$state | ConvertTo-Json -Depth 5 | Set-Content -LiteralPath $pluginStateFile -Encoding utf8
$meta = [ordered]@{
installed_at = $installedAt
source = "install-codex.ps1"
install_mode = "native-plugin"
hook_runtime = $hookRuntime
hooks_installed = $false
lifecycle_commands = @("ao rpi phased", "ao codex status")
plugin_key = $PluginKey
version = $InstallRef
plugin_root = $pluginCacheRoot
manifest_hash = $manifestHash
skill_count = $skillCount
plugin_state_file = $pluginStateFile
user_skills_root = $null
update_command = $UpdateCommand
}
$meta | ConvertTo-Json -Depth 5 | Set-Content -LiteralPath $installMeta -Encoding utf8
Remove-TomlKey $configFile "[features]" "hooks"
Write-Info "Codex hooks not installed (hookless - skills + ao CLI only)"
Write-Info "Native Codex plugin installed"
Write-Host " Plugin key: $PluginKey"
Write-Host " Plugin root: $pluginCacheRoot"
Write-Host " Skills available: $skillCount"
Write-Host " Config updated: $configFile"
if ($legacyBackup) {
Write-Host " Archived overlapping ~/.codex/skills entries to: $legacyBackup"
}
if ($userBackup) {
Write-Host " Archived overlapping ~/.agents/skills entries to: $userBackup"
}
Write-Info "Install metadata written: $installMeta"
Write-Host ""
Write-Host "Restart Codex to pick up the native plugin."
} finally {
if ($tempDir -and (Test-Path -LiteralPath $tempDir)) {
Remove-Item -LiteralPath $tempDir -Recurse -Force
}
}