Skip to content
Closed
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ bin/gstack-global-discover*
.factory/
.kiro/
.opencode/
.copilot/
.slate/
.cursor/
.openclaw/
Expand Down
5 changes: 1 addition & 4 deletions gstack-upgrade/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,9 +95,6 @@ elif [ -d "$HOME/.gstack/repos/gstack/.git" ]; then
elif [ -d ".claude/skills/gstack/.git" ]; then
INSTALL_TYPE="local-git"
INSTALL_DIR=".claude/skills/gstack"
elif [ -d ".agents/skills/gstack/.git" ]; then
INSTALL_TYPE="local-git"
INSTALL_DIR=".agents/skills/gstack"
elif [ -d ".claude/skills/gstack" ]; then
INSTALL_TYPE="vendored"
INSTALL_DIR=".claude/skills/gstack"
Expand Down Expand Up @@ -160,7 +157,7 @@ if [ -n "$_ROOT" ] && [ -d "$_ROOT/.claude/skills/gstack" ]; then
LOCAL_GSTACK="$_ROOT/.claude/skills/gstack"
fi
fi
_TEAM_MODE=$(~/.claude/skills/gstack/bin/gstack-config get team_mode 2>/dev/null || echo "false")
_TEAM_MODE=$($HOME/.claude/skills/gstack/bin/gstack-config get team_mode 2>/dev/null || echo "false")
echo "LOCAL_GSTACK=$LOCAL_GSTACK"
echo "TEAM_MODE=$_TEAM_MODE"
```
Expand Down
43 changes: 20 additions & 23 deletions gstack-upgrade/SKILL.md.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ _AUTO=""
echo "AUTO_UPGRADE=$_AUTO"
```

**If `AUTO_UPGRADE=true` or `AUTO_UPGRADE=1`:** Skip AskUserQuestion. Log "Auto-upgrading gstack v{old} → v{new}..." and proceed directly to Step 2. If `./setup` fails during auto-upgrade, restore from backup (`.bak` directory) and warn the user: "Auto-upgrade failed — restored previous version. Run `/gstack-upgrade` manually to retry."
**If `AUTO_UPGRADE=true` or `AUTO_UPGRADE=1`:** Skip AskUserQuestion. Log "Auto-upgrading gstack v{old} → v{new}..." and proceed directly to Step 2. If `{{SETUP_COMMAND}}` fails during auto-upgrade, restore from backup (`.bak` directory) and warn the user: "Auto-upgrade failed — restored previous version. Run `/gstack-upgrade` manually to retry."

**Otherwise**, use AskUserQuestion:
- Question: "gstack **v{new}** is available (you're on v{old}). Upgrade now?"
Expand Down Expand Up @@ -83,24 +83,21 @@ Continue with the current skill.
### Step 2: Detect install type

```bash
if [ -d "$HOME/.claude/skills/gstack/.git" ]; then
if [ -d "{{HOST_GLOBAL_ROOT}}/.git" ]; then
INSTALL_TYPE="global-git"
INSTALL_DIR="$HOME/.claude/skills/gstack"
INSTALL_DIR="{{HOST_GLOBAL_ROOT}}"
elif [ -d "$HOME/.gstack/repos/gstack/.git" ]; then
INSTALL_TYPE="global-git"
INSTALL_DIR="$HOME/.gstack/repos/gstack"
elif [ -d ".claude/skills/gstack/.git" ]; then
elif [ -d "{{LOCAL_SKILL_ROOT}}/.git" ]; then
INSTALL_TYPE="local-git"
INSTALL_DIR=".claude/skills/gstack"
elif [ -d ".agents/skills/gstack/.git" ]; then
INSTALL_TYPE="local-git"
INSTALL_DIR=".agents/skills/gstack"
elif [ -d ".claude/skills/gstack" ]; then
INSTALL_DIR="{{LOCAL_SKILL_ROOT}}"
elif [ -d "{{LOCAL_SKILL_ROOT}}" ]; then
INSTALL_TYPE="vendored"
INSTALL_DIR=".claude/skills/gstack"
elif [ -d "$HOME/.claude/skills/gstack" ]; then
INSTALL_DIR="{{LOCAL_SKILL_ROOT}}"
elif [ -d "{{HOST_GLOBAL_ROOT}}" ]; then
INSTALL_TYPE="vendored-global"
INSTALL_DIR="$HOME/.claude/skills/gstack"
INSTALL_DIR="{{HOST_GLOBAL_ROOT}}"
else
echo "ERROR: gstack not found"
exit 1
Expand Down Expand Up @@ -128,7 +125,7 @@ cd "$INSTALL_DIR"
STASH_OUTPUT=$(git stash 2>&1)
git fetch origin
git reset --hard origin/main
./setup
{{SETUP_COMMAND}}
```
If `$STASH_OUTPUT` contains "Saved working directory", warn the user: "Note: local changes were stashed. Run `git stash pop` in the skill directory to restore them."

Expand All @@ -139,7 +136,7 @@ TMP_DIR=$(mktemp -d)
git clone --depth 1 https://github.com/garrytan/gstack.git "$TMP_DIR/gstack"
mv "$INSTALL_DIR" "$INSTALL_DIR.bak"
mv "$TMP_DIR/gstack" "$INSTALL_DIR"
cd "$INSTALL_DIR" && ./setup
cd "$INSTALL_DIR" && {{SETUP_COMMAND}}
rm -rf "$INSTALL_DIR.bak" "$TMP_DIR"
```

Expand All @@ -150,14 +147,14 @@ Use the install directory from Step 2. Check if there's also a local vendored co
```bash
_ROOT=$(git rev-parse --show-toplevel 2>/dev/null)
LOCAL_GSTACK=""
if [ -n "$_ROOT" ] && [ -d "$_ROOT/.claude/skills/gstack" ]; then
_RESOLVED_LOCAL=$(cd "$_ROOT/.claude/skills/gstack" && pwd -P)
if [ -n "$_ROOT" ] && [ -d "$_ROOT/{{LOCAL_SKILL_ROOT}}" ]; then
_RESOLVED_LOCAL=$(cd "$_ROOT/{{LOCAL_SKILL_ROOT}}" && pwd -P)
_RESOLVED_PRIMARY=$(cd "$INSTALL_DIR" && pwd -P)
if [ "$_RESOLVED_LOCAL" != "$_RESOLVED_PRIMARY" ]; then
LOCAL_GSTACK="$_ROOT/.claude/skills/gstack"
LOCAL_GSTACK="$_ROOT/{{LOCAL_SKILL_ROOT}}"
fi
fi
_TEAM_MODE=$(~/.claude/skills/gstack/bin/gstack-config get team_mode 2>/dev/null || echo "false")
_TEAM_MODE=$({{HOST_GLOBAL_ROOT}}/bin/gstack-config get team_mode 2>/dev/null || echo "false")
echo "LOCAL_GSTACK=$LOCAL_GSTACK"
echo "TEAM_MODE=$_TEAM_MODE"
```
Expand All @@ -166,9 +163,9 @@ echo "TEAM_MODE=$_TEAM_MODE"

```bash
cd "$_ROOT"
git rm -r --cached .claude/skills/gstack/ 2>/dev/null || true
if ! grep -qF '.claude/skills/gstack/' .gitignore 2>/dev/null; then
echo '.claude/skills/gstack/' >> .gitignore
git rm -r --cached {{LOCAL_SKILL_ROOT}}/ 2>/dev/null || true
if ! grep -qF '{{LOCAL_SKILL_ROOT}}/' .gitignore 2>/dev/null; then
echo '{{LOCAL_SKILL_ROOT}}/' >> .gitignore
fi
rm -rf "$LOCAL_GSTACK"
```
Expand All @@ -179,12 +176,12 @@ Tell user: "Removed vendored copy at `$LOCAL_GSTACK` (team mode active — globa
mv "$LOCAL_GSTACK" "$LOCAL_GSTACK.bak"
cp -Rf "$INSTALL_DIR" "$LOCAL_GSTACK"
rm -rf "$LOCAL_GSTACK/.git"
cd "$LOCAL_GSTACK" && ./setup
cd "$LOCAL_GSTACK" && {{SETUP_COMMAND}}
rm -rf "$LOCAL_GSTACK.bak"
```
Tell user: "Also updated vendored copy at `$LOCAL_GSTACK` — commit `.claude/skills/gstack/` when you're ready."

If `./setup` fails, restore from backup and warn the user:
If `{{SETUP_COMMAND}}` fails, restore from backup and warn the user:
```bash
rm -rf "$LOCAL_GSTACK"
mv "$LOCAL_GSTACK.bak" "$LOCAL_GSTACK"
Expand Down
22 changes: 22 additions & 0 deletions hosts/copilot.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import type { HostConfig } from '../scripts/host-config';
import opencode from './opencode';

const copilot: HostConfig = {
...opencode,
name: 'copilot',
displayName: 'GitHub Copilot CLI',
cliCommand: 'gh',
cliAliases: [],

globalRoot: '.copilot/skills/gstack',
localSkillRoot: '.copilot/skills/gstack',
hostSubdir: '.copilot',

pathRewrites: [
{ from: '~/.claude/skills/gstack', to: '~/.copilot/skills/gstack' },
{ from: '.claude/skills/gstack', to: '.copilot/skills/gstack' },
{ from: '.claude/skills', to: '.copilot/skills' },
],
};

export default copilot;
5 changes: 3 additions & 2 deletions hosts/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,15 @@ import codex from './codex';
import factory from './factory';
import kiro from './kiro';
import opencode from './opencode';
import copilot from './copilot';
import slate from './slate';
import cursor from './cursor';
import openclaw from './openclaw';
import hermes from './hermes';
import gbrain from './gbrain';

/** All registered host configs. Add new hosts here. */
export const ALL_HOST_CONFIGS: HostConfig[] = [claude, codex, factory, kiro, opencode, slate, cursor, openclaw, hermes, gbrain];
export const ALL_HOST_CONFIGS: HostConfig[] = [claude, codex, factory, kiro, opencode, copilot, slate, cursor, openclaw, hermes, gbrain];

/** Map from host name to config. */
export const HOST_CONFIG_MAP: Record<string, HostConfig> = Object.fromEntries(
Expand Down Expand Up @@ -65,4 +66,4 @@ export function getExternalHosts(): HostConfig[] {
}

// Re-export individual configs for direct import
export { claude, codex, factory, kiro, opencode, slate, cursor, openclaw, hermes, gbrain };
export { claude, codex, factory, kiro, opencode, copilot, slate, cursor, openclaw, hermes, gbrain };
4 changes: 4 additions & 0 deletions scripts/resolvers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/

import type { TemplateContext, ResolverFn, ResolverValue } from './types';
import { getHostConfig } from '../../hosts/index';

// Domain modules
import { generatePreamble } from './preamble';
Expand Down Expand Up @@ -88,6 +89,9 @@ export const RESOLVERS: Record<string, ResolverValue> = {
MODEL_OVERLAY: generateModelOverlay,
TASTE_PROFILE: generateTasteProfile,
BIN_DIR: (ctx) => ctx.paths.binDir,
HOST_GLOBAL_ROOT: (ctx) => `$HOME/${getHostConfig(ctx.host).globalRoot}`,
LOCAL_SKILL_ROOT: (ctx) => ctx.paths.localSkillRoot,
SETUP_COMMAND: (ctx) => ctx.host === 'claude' ? './setup' : `./setup --host ${ctx.host}`,
GBRAIN_CONTEXT_LOAD: generateGBrainContextLoad,
GBRAIN_SAVE_RESULTS: generateGBrainSaveResults,
BRAIN_PREFLIGHT: generateBrainPreflight,
Expand Down
119 changes: 115 additions & 4 deletions setup
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ FACTORY_SKILLS="$HOME/.factory/skills"
FACTORY_GSTACK="$FACTORY_SKILLS/gstack"
OPENCODE_SKILLS="$HOME/.config/opencode/skills"
OPENCODE_GSTACK="$OPENCODE_SKILLS/gstack"
COPILOT_SKILLS="$HOME/.copilot/skills"
COPILOT_GSTACK="$COPILOT_SKILLS/gstack"

IS_WINDOWS=0
case "$(uname -s)" in
Expand Down Expand Up @@ -85,7 +87,7 @@ NO_TEAM_MODE=0
PLAN_TUNE_HOOKS_MODE="" # "" = resolve from env/config/prompt; "yes"/"no" = explicit
while [ $# -gt 0 ]; do
case "$1" in
--host) [ -z "$2" ] && echo "Missing value for --host (expected claude, codex, kiro, factory, opencode, openclaw, hermes, gbrain, or auto)" >&2 && exit 1; HOST="$2"; shift 2 ;;
--host) [ -z "$2" ] && echo "Missing value for --host (expected claude, codex, kiro, factory, opencode, copilot, openclaw, hermes, gbrain, or auto)" >&2 && exit 1; HOST="$2"; shift 2 ;;
--host=*) HOST="${1#--host=}"; shift ;;
--local) LOCAL_INSTALL=1; shift ;;
--prefix) SKILL_PREFIX=1; SKILL_PREFIX_FLAG=1; shift ;;
Expand All @@ -101,7 +103,7 @@ while [ $# -gt 0 ]; do
done

case "$HOST" in
claude|codex|kiro|factory|opencode|auto) ;;
claude|codex|kiro|factory|opencode|copilot|auto) ;;
openclaw)
echo ""
echo "OpenClaw integration uses a different model — OpenClaw spawns Claude Code"
Expand Down Expand Up @@ -136,7 +138,7 @@ case "$HOST" in
echo "GBrain setup and brain skills ship from the GBrain repo."
echo ""
exit 0 ;;
*) echo "Unknown --host value: $HOST (expected claude, codex, kiro, factory, opencode, openclaw, hermes, gbrain, or auto)" >&2; exit 1 ;;
*) echo "Unknown --host value: $HOST (expected claude, codex, kiro, factory, opencode, copilot, openclaw, hermes, gbrain, or auto)" >&2; exit 1 ;;
esac

# ─── Resolve skill prefix preference ─────────────────────────
Expand Down Expand Up @@ -200,14 +202,16 @@ INSTALL_CODEX=0
INSTALL_KIRO=0
INSTALL_FACTORY=0
INSTALL_OPENCODE=0
INSTALL_COPILOT=0
if [ "$HOST" = "auto" ]; then
command -v claude >/dev/null 2>&1 && INSTALL_CLAUDE=1
command -v codex >/dev/null 2>&1 && INSTALL_CODEX=1
command -v kiro-cli >/dev/null 2>&1 && INSTALL_KIRO=1
command -v droid >/dev/null 2>&1 && INSTALL_FACTORY=1
command -v opencode >/dev/null 2>&1 && INSTALL_OPENCODE=1
command -v gh >/dev/null 2>&1 && [ -d "$HOME/.copilot" ] && INSTALL_COPILOT=1
# If none found, default to claude
if [ "$INSTALL_CLAUDE" -eq 0 ] && [ "$INSTALL_CODEX" -eq 0 ] && [ "$INSTALL_KIRO" -eq 0 ] && [ "$INSTALL_FACTORY" -eq 0 ] && [ "$INSTALL_OPENCODE" -eq 0 ]; then
if [ "$INSTALL_CLAUDE" -eq 0 ] && [ "$INSTALL_CODEX" -eq 0 ] && [ "$INSTALL_KIRO" -eq 0 ] && [ "$INSTALL_FACTORY" -eq 0 ] && [ "$INSTALL_OPENCODE" -eq 0 ] && [ "$INSTALL_COPILOT" -eq 0 ]; then
INSTALL_CLAUDE=1
fi
elif [ "$HOST" = "claude" ]; then
Expand All @@ -220,6 +224,8 @@ elif [ "$HOST" = "factory" ]; then
INSTALL_FACTORY=1
elif [ "$HOST" = "opencode" ]; then
INSTALL_OPENCODE=1
elif [ "$HOST" = "copilot" ]; then
INSTALL_COPILOT=1
fi

migrate_direct_codex_install() {
Expand Down Expand Up @@ -475,6 +481,16 @@ if [ "$INSTALL_OPENCODE" -eq 1 ] && [ "$NEEDS_BUILD" -eq 0 ]; then
)
fi

# 1e. Generate .copilot/ GitHub Copilot CLI skill docs
if [ "$INSTALL_COPILOT" -eq 1 ] && [ "$NEEDS_BUILD" -eq 0 ]; then
log "Generating .copilot/ skill docs..."
(
cd "$SOURCE_GSTACK_DIR"
bun_cmd install --frozen-lockfile 2>/dev/null || bun_cmd install
bun_cmd run gen:skill-docs --host copilot
)
fi

# 2. Ensure Playwright's Chromium is available
if ! ensure_playwright_browser; then
echo "Installing Playwright Chromium..."
Expand Down Expand Up @@ -908,6 +924,59 @@ create_opencode_runtime_root() {
fi
}

create_copilot_runtime_root() {
local gstack_dir="$1"
local copilot_gstack="$2"
local copilot_dir="$gstack_dir/.copilot/skills"

if [ -L "$copilot_gstack" ]; then
rm -f "$copilot_gstack"
elif [ -d "$copilot_gstack" ] && [ "$copilot_gstack" != "$gstack_dir" ]; then
rm -rf "$copilot_gstack"
fi

mkdir -p "$copilot_gstack" "$copilot_gstack/browse" "$copilot_gstack/design" "$copilot_gstack/gstack-upgrade" "$copilot_gstack/review" "$copilot_gstack/qa" "$copilot_gstack/plan-devex-review"

if [ -f "$copilot_dir/gstack/SKILL.md" ]; then
_link_or_copy "$copilot_dir/gstack/SKILL.md" "$copilot_gstack/SKILL.md"
fi
if [ -d "$gstack_dir/bin" ]; then
_link_or_copy "$gstack_dir/bin" "$copilot_gstack/bin"
fi
if [ -d "$gstack_dir/browse/dist" ]; then
_link_or_copy "$gstack_dir/browse/dist" "$copilot_gstack/browse/dist"
fi
if [ -d "$gstack_dir/browse/bin" ]; then
_link_or_copy "$gstack_dir/browse/bin" "$copilot_gstack/browse/bin"
fi
if [ -d "$gstack_dir/design/dist" ]; then
_link_or_copy "$gstack_dir/design/dist" "$copilot_gstack/design/dist"
fi
if [ -f "$copilot_dir/gstack-upgrade/SKILL.md" ]; then
_link_or_copy "$copilot_dir/gstack-upgrade/SKILL.md" "$copilot_gstack/gstack-upgrade/SKILL.md"
fi
for f in checklist.md design-checklist.md greptile-triage.md TODOS-format.md; do
if [ -f "$gstack_dir/review/$f" ]; then
_link_or_copy "$gstack_dir/review/$f" "$copilot_gstack/review/$f"
fi
done
if [ -d "$gstack_dir/review/specialists" ]; then
_link_or_copy "$gstack_dir/review/specialists" "$copilot_gstack/review/specialists"
fi
if [ -d "$gstack_dir/qa/templates" ]; then
_link_or_copy "$gstack_dir/qa/templates" "$copilot_gstack/qa/templates"
fi
if [ -d "$gstack_dir/qa/references" ]; then
_link_or_copy "$gstack_dir/qa/references" "$copilot_gstack/qa/references"
fi
if [ -f "$gstack_dir/plan-devex-review/dx-hall-of-fame.md" ]; then
_link_or_copy "$gstack_dir/plan-devex-review/dx-hall-of-fame.md" "$copilot_gstack/plan-devex-review/dx-hall-of-fame.md"
fi
if [ -f "$gstack_dir/ETHOS.md" ]; then
_link_or_copy "$gstack_dir/ETHOS.md" "$copilot_gstack/ETHOS.md"
fi
}

link_factory_skill_dirs() {
local gstack_dir="$1"
local skills_dir="$2"
Expand Down Expand Up @@ -972,6 +1041,38 @@ link_opencode_skill_dirs() {
fi
}

link_copilot_skill_dirs() {
local gstack_dir="$1"
local skills_dir="$2"
local copilot_dir="$gstack_dir/.copilot/skills"
local linked=()

if [ ! -d "$copilot_dir" ]; then
echo " Generating .copilot/ skill docs..."
( cd "$gstack_dir" && bun run gen:skill-docs --host copilot )
fi

if [ ! -d "$copilot_dir" ]; then
echo " warning: .copilot/skills/ generation failed — run 'bun run gen:skill-docs --host copilot' manually" >&2
return 1
fi

for skill_dir in "$copilot_dir"/gstack*/; do
if [ -f "$skill_dir/SKILL.md" ]; then
skill_name="$(basename "$skill_dir")"
[ "$skill_name" = "gstack" ] && continue
target="$skills_dir/$skill_name"
if [ -L "$target" ] || [ ! -e "$target" ]; then
_link_or_copy "$skill_dir" "$target"
linked+=("$skill_name")
fi
fi
done
if [ ${#linked[@]} -gt 0 ]; then
echo " linked skills: ${linked[*]}"
fi
}

# 4. Install for Claude (default)
SKILLS_BASENAME="$(basename "$INSTALL_SKILLS_DIR")"
SKILLS_PARENT_BASENAME="$(basename "$(dirname "$INSTALL_SKILLS_DIR")")"
Expand Down Expand Up @@ -1193,6 +1294,16 @@ if [ "$INSTALL_OPENCODE" -eq 1 ]; then
echo " opencode skills: $OPENCODE_SKILLS"
fi

# 6d. Install for GitHub Copilot CLI
if [ "$INSTALL_COPILOT" -eq 1 ]; then
mkdir -p "$COPILOT_SKILLS"
create_copilot_runtime_root "$SOURCE_GSTACK_DIR" "$COPILOT_GSTACK"
link_copilot_skill_dirs "$SOURCE_GSTACK_DIR" "$COPILOT_SKILLS"
echo "gstack ready (copilot)."
echo " browse: $BROWSE_BIN"
echo " copilot skills: $COPILOT_SKILLS"
fi

# 7. Create .agents/ sidecar symlinks for the real Codex skill target.
# The root Codex skill ends up pointing at $SOURCE_GSTACK_DIR/.agents/skills/gstack,
# so the runtime assets must live there for both global and repo-local installs.
Expand Down
Loading