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: 6 additions & 0 deletions .zsh/aliases.zsh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion env-contract.json
Original file line number Diff line number Diff line change
Expand Up @@ -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)" }
]
}
12 changes: 8 additions & 4 deletions powershell/profile.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
46 changes: 23 additions & 23 deletions setup-linux.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
66 changes: 31 additions & 35 deletions setup-windows.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand All @@ -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"
}
Expand Down Expand Up @@ -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)
Expand Down
38 changes: 38 additions & 0 deletions tests/aliases.bats
Original file line number Diff line number Diff line change
Expand Up @@ -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" {
Expand Down Expand Up @@ -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)" {
Expand Down
26 changes: 26 additions & 0 deletions tests/setup-windows.bats
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
Expand Down Expand Up @@ -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)" {
Expand Down
Loading