diff --git a/.zsh/aliases.zsh b/.zsh/aliases.zsh index 9701b63..cf958eb 100644 --- a/.zsh/aliases.zsh +++ b/.zsh/aliases.zsh @@ -35,6 +35,12 @@ alias cl="changelog-gen.sh" # Regenerate CHANGELOG.md from alias oc="opencode" # TUI: opencode Go subscription alias oclog='tail -F "$(ls -t ~/.local/share/opencode/log/*.log | head -1)" | grep --line-buffered -vE "file\.watcher\.updated|bus type=message\.part\.delta"' # live tail of newest opencode log, filtered +# GitHub Copilot CLI v2 (BUG-003: standalone agentic CLI, replaces ghcs/ghce wrappers) +# cop -> interactive agent (tool use requires confirmation, safe default) +# cops -> single-shot non-interactive prompt with --allow-all-tools (required by CLI for -p mode) +alias cop="copilot" +cops() { copilot -p "$*" --allow-all-tools -s; } + # tmux session management alias tx='tmux new -A -s' # attach-or-create by name: tx dotfiles alias txl='tmux ls' # list sessions diff --git a/env-contract.json b/env-contract.json index 87acaa3..94bc0f1 100644 --- a/env-contract.json +++ b/env-contract.json @@ -64,7 +64,8 @@ { "name": "npx", "purpose": "MCP server stdio transports" }, { "name": "npm", "purpose": "claude-mem-heal zod install" }, { "name": "age", "purpose": "secrets encryption" }, - { "name": "gh", "purpose": "GitHub CLI + Copilot extension" }, + { "name": "gh", "purpose": "GitHub CLI (PR/issue management; Copilot CLI is the standalone `copilot` binary, not a gh extension since BUG-003)" }, + { "name": "copilot", "purpose": "GitHub Copilot CLI (agentic assistant; winget GitHub.Copilot on Windows, manual install on Linux)" }, { "name": "uv", "purpose": "Python tool installs (hive-vault, general Python workflows)" } ] } diff --git a/powershell/profile.ps1 b/powershell/profile.ps1 index 3d00b87..94e2fe2 100644 --- a/powershell/profile.ps1 +++ b/powershell/profile.ps1 @@ -67,10 +67,14 @@ function gd { git diff } function gl { git log --oneline -10 } function gp { git pull } -# GitHub Copilot CLI aliases -if (Get-Command gh -ErrorAction SilentlyContinue) { - function ghcs { gh copilot suggest @args } - function ghce { gh copilot explain @args } +# GitHub Copilot CLI v2 aliases (BUG-003: standalone agentic `copilot`, not gh extension) +# - cop -> interactive agent (default safe: tool use needs confirmation) +# - cops -> single-shot non-interactive prompt; --allow-all-tools is REQUIRED by +# the CLI for non-interactive mode and gives the agent edit/exec power. +# Use 'cop' instead when you want per-tool confirmation. +if (Get-Command copilot -ErrorAction SilentlyContinue) { + Set-Alias -Name cop -Value copilot + function cops { copilot -p "$args" --allow-all-tools -s } } # Enhanced listing (requires eza) diff --git a/setup-linux.sh b/setup-linux.sh index fed37d9..c3d4821 100755 --- a/setup-linux.sh +++ b/setup-linux.sh @@ -501,33 +501,33 @@ else log_warning "ghostty config source missing: $GHOSTTY_CONFIG_SRC" fi -# GitHub Copilot CLI (detect-and-act: deploy config only when the extension -# is actually installed; the script does NOT auto-install the extension on -# Linux — gh is widely used for PR/issue management without Copilot intent. -# To enable: gh extension install github/gh-copilot, then re-run setup). -# Verification string updated for AI-013 pointer-style copilot-instructions.md. -if command -v gh >/dev/null 2>&1; then - if gh extension list 2>/dev/null | grep -q "github/gh-copilot"; then - log_info "GitHub Copilot CLI extension detected, deploying configuration..." - ensure_directory "$HOME/.copilot" - cp -rf "$CURRENT_DIR/ai/copilot/"* "$HOME/.copilot/" 2>/dev/null || true - if [ -f "$HOME/.copilot/copilot-instructions.md" ] && grep -q 'First, read `AGENTS.md`' "$HOME/.copilot/copilot-instructions.md"; then - log_success "copilot-instructions.md deployed successfully (verified pointer to AGENTS.md)" - else - log_warning "copilot-instructions.md deployment failed verification (expected pointer to AGENTS.md)" - fi - log_success "GitHub Copilot CLI configured" +# GitHub Copilot CLI (BUG-003: standalone agentic CLI, drops legacy gh-copilot +# extension path). Linux side is detect-and-act -- no auto-install (distros vary; +# user installs via snap/apt/curl per https://docs.github.com/copilot/how-tos/copilot-cli). +# Verification string set by AI-013 (pointer-style copilot-instructions.md). +# +# Cleanup (idempotent): the previous setup added 'eval "$(gh copilot alias -- bash)"' +# to .zshrc/.bashrc. That subcommand does not exist in the new standalone CLI, so +# the line errors silently on every shell startup. Strip it if present. +for rc in "$HOME/.zshrc" "$HOME/.bashrc"; do + if [ -f "$rc" ] && grep -qF 'eval "$(gh copilot alias -- bash)"' "$rc"; then + sed -i '/eval "$(gh copilot alias -- bash)"/d' "$rc" + log_info "Removed stale gh-copilot eval line from $rc" + fi +done - # Aliases - log_info "Adding Copilot aliases to .zshrc/.bashrc..." - COPILOT_SUGGEST='eval "$(gh copilot alias -- bash)"' - [ -f "$HOME/.zshrc" ] && ensure_line_in_file "$HOME/.zshrc" "$COPILOT_SUGGEST" - [ -f "$HOME/.bashrc" ] && ensure_line_in_file "$HOME/.bashrc" "$COPILOT_SUGGEST" +if command -v copilot >/dev/null 2>&1; then + log_info "GitHub Copilot CLI detected, deploying configuration..." + ensure_directory "$HOME/.copilot" + cp -rf "$CURRENT_DIR/ai/copilot/"* "$HOME/.copilot/" 2>/dev/null || true + if [ -f "$HOME/.copilot/copilot-instructions.md" ] && grep -q 'First, read `AGENTS.md`' "$HOME/.copilot/copilot-instructions.md"; then + log_success "copilot-instructions.md deployed successfully (verified pointer to AGENTS.md)" else - log_info "GitHub Copilot CLI extension not installed, skipping Copilot config (install with: gh extension install github/gh-copilot)" + log_warning "copilot-instructions.md deployment failed verification (expected pointer to AGENTS.md)" fi + log_success "GitHub Copilot CLI configured (aliases cop/cops in .zsh/aliases.zsh)" else - log_warning "GitHub CLI (gh) not found, skipping Copilot setup" + log_info "GitHub Copilot CLI not installed, skipping Copilot config (install via snap/apt/curl: https://docs.github.com/copilot/how-tos/copilot-cli)" fi # Register MCP servers (requires Claude Code CLI, Node.js, jq) diff --git a/setup-windows.ps1 b/setup-windows.ps1 index d9f6880..3190220 100644 --- a/setup-windows.ps1 +++ b/setup-windows.ps1 @@ -104,7 +104,8 @@ if ($wingetCmd) { @{ Name = "eza"; Cmd = "eza"; Id = "eza-community.eza" }, @{ Name = "jq"; Cmd = "jq"; Id = "jqlang.jq" }, @{ Name = "GitHub CLI"; Cmd = "gh"; Id = "GitHub.cli" }, - @{ Name = "zoxide"; Cmd = "zoxide"; Id = "ajeetdsouza.zoxide" } + @{ Name = "zoxide"; Cmd = "zoxide"; Id = "ajeetdsouza.zoxide" }, + @{ Name = "GitHub Copilot CLI"; Cmd = "copilot"; Id = "GitHub.Copilot" } ) foreach ($tool in $tools) { if (-not (Get-Command $tool.Cmd -ErrorAction SilentlyContinue)) { @@ -119,6 +120,11 @@ if ($wingetCmd) { Write-Info "$($tool.Name) already installed" } } + # Refresh PATH so freshly-installed winget tools are visible to subsequent + # blocks of this same setup run (otherwise Get-Command misses them until + # the next shell start; first introduced for BUG-003 so the Copilot config + # deploy block sees the just-installed `copilot` binary). + $env:PATH = [Environment]::GetEnvironmentVariable("PATH", "Machine") + ";" + [Environment]::GetEnvironmentVariable("PATH", "User") } else { Write-Warn "winget not found, skipping developer tools installation" } @@ -792,43 +798,33 @@ if (Test-Path $ClaudeSettings) { Write-Info "Setting up GitHub Copilot CLI..." -# Detect-and-act: deploy config only when gh AND the gh-copilot extension -# are actually installed. The script does NOT auto-install the extension -- -# gh is widely used for PR/issue management on machines that do not want -# Copilot. To enable: gh extension install github/gh-copilot, then re-run. -# Verification string updated for AI-013 pointer-style copilot-instructions.md. -$ghCmd = Get-Command gh -ErrorAction SilentlyContinue -if ($ghCmd) { - $copilotInstalled = $false - try { - $extensions = & gh extension list 2>$null - $copilotInstalled = ($extensions -match 'github/gh-copilot') - } catch { - $copilotInstalled = $false - } - - if ($copilotInstalled) { - Write-Info "GitHub Copilot CLI extension detected, deploying configuration..." - $CopilotHome = "$env:USERPROFILE\.copilot" - Ensure-Directory $CopilotHome - - $copilotSource = "$DotfilesDir\ai\copilot" - if (Test-Path $copilotSource) { - Copy-Item "$copilotSource\*" "$CopilotHome\" -Recurse -Force -ErrorAction SilentlyContinue - if ((Test-Path "$CopilotHome\copilot-instructions.md") -and - (Select-String -Path "$CopilotHome\copilot-instructions.md" -Pattern 'First, read `AGENTS.md`' -SimpleMatch -Quiet)) { - Write-Success "copilot-instructions.md deployed successfully (verified pointer to AGENTS.md)" - } else { - Write-Warn "copilot-instructions.md deployment failed verification (expected pointer to AGENTS.md)" - } +# BUG-003: detect the new standalone `copilot` CLI (winget GitHub.Copilot, +# agentic interface, closer to Claude Code than to the legacy gh-copilot +# extension's suggest/explain wrappers). The dev tools winget block above +# auto-installs it; this block deploys config when the binary is on PATH. +# Note: AWS Copilot CLI (Amazon.CopilotCLI) also exposes itself as `copilot`. +# If both are installed, Get-Command resolves to the first on PATH. Out-of- +# scope to disambiguate here; <1% population. +$copilotCmd = Get-Command copilot -ErrorAction SilentlyContinue +if ($copilotCmd) { + Write-Info "GitHub Copilot CLI detected at $($copilotCmd.Source), deploying configuration..." + $CopilotHome = "$env:USERPROFILE\.copilot" + Ensure-Directory $CopilotHome + + $copilotSource = "$DotfilesDir\ai\copilot" + if (Test-Path $copilotSource) { + Copy-Item "$copilotSource\*" "$CopilotHome\" -Recurse -Force -ErrorAction SilentlyContinue + if ((Test-Path "$CopilotHome\copilot-instructions.md") -and + (Select-String -Path "$CopilotHome\copilot-instructions.md" -Pattern 'First, read `AGENTS.md`' -SimpleMatch -Quiet)) { + Write-Success "copilot-instructions.md deployed successfully (verified pointer to AGENTS.md)" + } else { + Write-Warn "copilot-instructions.md deployment failed verification (expected pointer to AGENTS.md)" } - - Write-Success "GitHub Copilot CLI configured (aliases ghcs/ghce in profile.ps1)" - } else { - Write-Info "GitHub Copilot CLI extension not installed, skipping Copilot config (install with: gh extension install github/gh-copilot)" } + + Write-Success "GitHub Copilot CLI configured (aliases cop/cops in profile.ps1)" } else { - Write-Warn "GitHub CLI (gh) not found, skipping Copilot setup" + Write-Info "GitHub Copilot CLI not installed; the dev tools block above attempts auto-install via winget GitHub.Copilot. Re-run setup or open a new shell if the binary was just installed and PATH needs refresh." } # Weekly vault maintenance scheduled task (Sundays 10:07 AM) diff --git a/tests/aliases.bats b/tests/aliases.bats index c608de6..036a3c5 100644 --- a/tests/aliases.bats +++ b/tests/aliases.bats @@ -49,6 +49,28 @@ setup() { ! grep -qE '^alias (ai|aic|aia)=' "$ALIASES_FILE" } +# --- Copilot CLI v2 aliases (BUG-003: rename from ghcs/ghce) --- +# The old aliases wrapped 'gh copilot suggest/explain' which do not exist in +# the new standalone 'copilot' CLI. Rename to 'cop' (interactive agentic CLI) +# and 'cops' (single-shot non-interactive prompt with --allow-all-tools). + +@test "aliases.zsh defines cop alias for copilot" { + grep -qE '^alias cop="copilot"' "$ALIASES_FILE" +} + +@test "aliases.zsh defines cops function for single-shot copilot prompt" { + grep -qE '^cops\(\)' "$ALIASES_FILE" + grep -qF 'copilot -p' "$ALIASES_FILE" + grep -qF -- '--allow-all-tools' "$ALIASES_FILE" +} + +@test "aliases.zsh no longer defines ghcs/ghce (renamed in BUG-003)" { + # Anchor to start-of-line + alias/function definition forms only -- comments + # mentioning the old names (e.g. "replaces ghcs/ghce wrappers") are fine. + ! grep -qE '^(alias ghcs|ghcs\(\)|function ghcs)' "$ALIASES_FILE" + ! grep -qE '^(alias ghce|ghce\(\)|function ghce)' "$ALIASES_FILE" +} + # --- Knowledge aliases --- @test "aliases.zsh defines kc alias" { @@ -107,6 +129,22 @@ setup() { grep -q 'function gp' "$ps_file" } +@test "parity: cop and cops exist in both aliases.zsh and profile.ps1" { + ps_file="$DOTFILES_DIR/powershell/profile.ps1" + grep -qE '^alias cop="copilot"' "$ALIASES_FILE" + grep -qE 'Set-Alias -Name cop -Value copilot' "$ps_file" + grep -qE '^cops\(\)' "$ALIASES_FILE" + grep -qE 'function cops' "$ps_file" +} + +@test "parity: ghcs/ghce removed from both aliases.zsh and profile.ps1" { + ps_file="$DOTFILES_DIR/powershell/profile.ps1" + ! grep -qE '^(alias ghcs|ghcs\(\)|function ghcs)' "$ALIASES_FILE" + ! grep -qE '^(alias ghce|ghce\(\)|function ghce)' "$ALIASES_FILE" + ! grep -qE '^\s*function ghcs' "$ps_file" + ! grep -qE '^\s*function ghce' "$ps_file" +} + # --- tmux aliases --- @test "aliases.zsh defines tx alias (attach-or-create with -A)" { diff --git a/tests/setup-windows.bats b/tests/setup-windows.bats index 7743bcb..aa80380 100644 --- a/tests/setup-windows.bats +++ b/tests/setup-windows.bats @@ -57,6 +57,24 @@ setup() { grep -q 'Setting up GitHub Copilot CLI' "$PS1_SCRIPT" } +# --- BUG-003: drop gh-copilot extension path, detect new copilot CLI v2 --- +# The legacy 'gh extension install github/gh-copilot' product was the v1 wrapper +# around suggest/explain. The new 'copilot' standalone CLI (winget GitHub.Copilot, +# binary `copilot`) is agentic (closer to Claude Code). Setup detects the new one +# via 'Get-Command copilot' and stops referencing the old extension entirely. + +@test "setup-windows.ps1 detects copilot via Get-Command, not gh extension list" { + grep -qF "Get-Command copilot" "$PS1_SCRIPT" + ! grep -qF "gh extension list" "$PS1_SCRIPT" + ! grep -qF "github/gh-copilot" "$PS1_SCRIPT" +} + +@test "setup-windows.ps1 auto-installs GitHub.Copilot via winget" { + grep -qF "GitHub.Copilot" "$PS1_SCRIPT" + # In the dev tools winget block, the binary check uses `copilot` + grep -qE 'Name = "GitHub Copilot CLI"; Cmd = "copilot"; Id = "GitHub\.Copilot"' "$PS1_SCRIPT" +} + @test "setup-windows.ps1 deploys init-project.ps1" { grep -q 'init-project.ps1' "$PS1_SCRIPT" } @@ -245,6 +263,14 @@ setup() { ! grep -qF -- '-Pattern "CORE PRINCIPLE"' "$PS1_SCRIPT" } +@test "parity: both setup scripts detect copilot via binary, not gh extension" { + grep -qF "command -v copilot" "$DOTFILES_DIR/setup-linux.sh" + grep -qF "Get-Command copilot" "$PS1_SCRIPT" + # Neither should still reference the legacy extension path + ! grep -qF "github/gh-copilot" "$DOTFILES_DIR/setup-linux.sh" + ! grep -qF "github/gh-copilot" "$PS1_SCRIPT" +} + # --- PSScriptAnalyzer --- @test "setup-windows.ps1 passes PSScriptAnalyzer (if pwsh available)" {