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 @@ -17,6 +17,7 @@ bin/gstack-global-discover
.cursor/
.openclaw/
.hermes/
.forgecode/
.gbrain/
.context/
extension/.auth.json
Expand Down
48 changes: 48 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,53 @@
# Changelog

## [1.29.1.0] - 2026-05-08

### Fixed
- **Forge Code skills now actually load.** The 1.29.0 install wrote skills to `~/.forgecode/skills/`, but Forge Code 2.x discovers user skills from `~/.agents/skills/`. `forge list skill` would report only the four built-in skills even after a clean install. Setup now installs to `~/.agents/skills/gstack-*` and the runtime root at `~/.agents/skills/gstack/`. Existing installs at the legacy `~/.forgecode/skills/` location are migrated (cleaned up) on the next `./setup --host forgecode` run.
- `hosts/forgecode.ts`: `globalRoot` and `pathRewrites` now point at `.agents/skills/gstack` so paths baked into generated SKILL.md files resolve at runtime.
- **Skill name matches parent directory.** Other harnesses that share `~/.agents/skills/` (notably `pi`) reject SKILL.md when the `name:` frontmatter doesn't match the parent directory. The install now copies (instead of symlinks) `SKILL.md` and patches the `name:` field to match `gstack-<skill>`. The build output under `.forgecode/skills/` is left unmodified so the freshness check still passes.
- `bin/gstack-patch-names`: pass `--color=never` to grep so colorized aliases don't poison the parsed name field.
- README + uninstall snippet updated to include `~/.agents/skills/gstack*`.

## [1.29.0.0] - 2026-05-08

## **Forge Code is now a first-class gstack host. Run `./setup --host forgecode` and your skills land in `.forgecode/skills/`.**

Forge Code joins the 10 existing supported agents. The install path is the same as OpenCode and Kiro: `./setup` auto-detects the `forge` binary, generates host-adapted SKILL.md files with Forge Code tool names and paths, creates the runtime root under `~/.forgecode/skills/gstack/`, and symlinks each generated skill into `~/.forgecode/skills/`. The `codex` skill is excluded (Forge Code users don't have the Codex CLI). All Claude-specific paths, tool references, and co-author trailers are rewritten for Forge Code conventions.

The three numbers that matter (measured against the opencode install path as the reference implementation):

Source: `diff --stat` against main + manual install verification in this environment.

| Metric | Before | After | Δ |
|---|---|---|---|
| Supported hosts | 10 | 11 | +1 |
| Setup validation tests | 371 | 392 | +21 |
| Host config validations | 10 configs valid | 11 configs valid | +1 |

One edge case fixed in auto-detection: `command -v forge` is ambiguous — Foundry (the Ethereum toolkit) also ships a `forge` binary. The auto-detect now uses `forge agent --help` as the discriminating check, which only succeeds for Forge Code.

**What this means for Forge Code users:** Run `./setup --host forgecode` once, and you get the full gstack skill suite adapted to Forge Code's tool model (`shell`, `patch`, `fs_search`, `sage` instead of Claude tool names). Uninstall: `rm -rf ~/.forgecode/skills/gstack*`.

### Itemized changes

#### Added
- `hosts/forgecode.ts`: new `HostConfig` for Forge Code with path rewrites, tool name mapping, suppressed resolvers, runtime root definition, and co-author trailer
- `./setup --host forgecode`: full install path including auto-detection via `forge agent`, skill generation, runtime root setup, and symlink linking
- `.forgecode/` to `.gitignore`
- README: install and uninstall instructions for Forge Code

#### Changed
- `hosts/index.ts`: Forge Code registered as the 11th host
- `scripts/skill-check.ts`: consolidated host imports at file top; `bun run skill:check` now respects primary-host `skipSkills` so Claude-only skipped templates don't produce false errors

#### Fixed
- Auto-detection in `--host auto` mode now uses `forge agent --help` instead of bare `command -v forge`, preventing false-positive installs on machines with Foundry's `forge` binary

#### For contributors
- `test/gen-skill-docs.test.ts`: 6 new tests for forgecode setup path, install flag vars, and runtime root structure
- `test/host-config.test.ts`: `ALL_HOST_CONFIGS` count bumped to 11; forgecode name asserted

## [1.28.0.0] - 2026-05-07

## **Browse handles real-world automation now: SOCKS5 with auth, container Xvfb, browser-native downloads. Plus a single-file `llms.txt` index agents can crawl in one read.**
Expand Down
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ These are conversational skills. Your OpenClaw agent runs them directly via chat

### Other AI Agents

gstack works on 10 AI coding agents, not just Claude. Setup auto-detects which
gstack works on 11 AI coding agents, not just Claude. Setup auto-detects which
agents you have installed:

```bash
Expand All @@ -117,6 +117,7 @@ Or target a specific agent with `./setup --host <name>`:
| OpenCode | `--host opencode` | `~/.config/opencode/skills/gstack-*/` |
| Cursor | `--host cursor` | `~/.cursor/skills/gstack-*/` |
| Factory Droid | `--host factory` | `~/.factory/skills/gstack-*/` |
| Forge Code | `--host forgecode` | `~/.agents/skills/gstack-*/` |
| Slate | `--host slate` | `~/.slate/skills/gstack-*/` |
| Kiro | `--host kiro` | `~/.kiro/skills/gstack-*/` |
| Hermes | `--host hermes` | `~/.hermes/skills/gstack-*/` |
Expand Down Expand Up @@ -343,6 +344,8 @@ rm -rf ~/.codex/skills/gstack* 2>/dev/null
rm -rf ~/.factory/skills/gstack* 2>/dev/null
rm -rf ~/.kiro/skills/gstack* 2>/dev/null
rm -rf ~/.openclaw/skills/gstack* 2>/dev/null
rm -rf ~/.forgecode/skills/gstack* 2>/dev/null
rm -rf ~/.agents/skills/gstack* 2>/dev/null

# 6. Remove temp files
rm -f /tmp/gstack-* 2>/dev/null
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.28.0.0
1.29.1.0
2 changes: 1 addition & 1 deletion bin/gstack-patch-names
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ for skill_dir in "$GSTACK_DIR"/*/; do
[ -f "$skill_dir/SKILL.md" ] || continue
dir_name="$(basename "$skill_dir")"
[ "$dir_name" = "node_modules" ] && continue
cur=$(grep -m1 '^name:' "$skill_dir/SKILL.md" 2>/dev/null | sed 's/^name:[[:space:]]*//' | tr -d '[:space:]' || true)
cur=$(grep --color=never -m1 '^name:' "$skill_dir/SKILL.md" 2>/dev/null | sed 's/^name:[[:space:]]*//' | tr -d '[:space:]' || true)
[ -z "$cur" ] && continue
[ "$cur" = "gstack" ] && continue # never prefix root skill
if [ "$DO_PREFIX" -eq 1 ]; then
Expand Down
73 changes: 73 additions & 0 deletions hosts/forgecode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import type { HostConfig } from '../scripts/host-config';

const forgecode: HostConfig = {
name: 'forgecode',
displayName: 'Forge Code',
cliCommand: 'forge',
cliAliases: ['forge-code'],

// Forge Code 2.x discovers user skills from ~/.agents/skills/. The repo-side
// build output stays under .forgecode/ (see hostSubdir) but the install path
// (globalRoot) and embedded SKILL.md path references must point at the real
// discovery location.
globalRoot: '.agents/skills/gstack',
localSkillRoot: '.agents/skills/gstack',
hostSubdir: '.forgecode',
usesEnvVars: true,

frontmatter: {
mode: 'allowlist',
keepFields: ['name', 'description'],
descriptionLimit: null,
},

generation: {
generateMetadata: false,
skipSkills: ['codex'],
},

pathRewrites: [
{ from: '~/.claude/skills/gstack', to: '~/.agents/skills/gstack' },
{ from: '.claude/skills/gstack', to: '.agents/skills/gstack' },
{ from: '.claude/skills', to: '.agents/skills' },
{ from: 'CLAUDE.md', to: 'AGENTS.md' },
],
toolRewrites: {
'use the Bash tool': 'use the shell tool',
'use the Write tool': 'use the patch tool',
'use the Edit tool': 'use the patch tool',
'use the Agent tool': 'use the sage tool',
'use the Grep tool': 'use the fs_search tool',
'use the Glob tool': 'use the fs_search tool',
'the Bash tool': 'the shell tool',
'the Write tool': 'the patch tool',
'the Edit tool': 'the patch tool',
},

suppressedResolvers: [
'DESIGN_OUTSIDE_VOICES',
'ADVERSARIAL_STEP',
'CODEX_SECOND_OPINION',
'CODEX_PLAN_REVIEW',
'REVIEW_ARMY',
'GBRAIN_CONTEXT_LOAD',
'GBRAIN_SAVE_RESULTS',
],

runtimeRoot: {
globalSymlinks: ['bin', 'browse/dist', 'browse/bin', 'design/dist', 'gstack-upgrade', 'ETHOS.md', 'review/specialists', 'qa/templates', 'qa/references', 'plan-devex-review/dx-hall-of-fame.md'],
globalFiles: {
'review': ['checklist.md', 'design-checklist.md', 'greptile-triage.md', 'TODOS-format.md'],
},
},

install: {
prefixable: false,
linkingStrategy: 'symlink-generated',
},

coAuthorTrailer: 'Co-Authored-By: Forge Code <noreply@forgecode.dev>',
learningsMode: 'basic',
};

export default forgecode;
5 changes: 3 additions & 2 deletions hosts/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@ import cursor from './cursor';
import openclaw from './openclaw';
import hermes from './hermes';
import gbrain from './gbrain';
import forgecode from './forgecode';

/** 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, slate, cursor, openclaw, hermes, gbrain, forgecode];

/** 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, slate, cursor, openclaw, hermes, gbrain, forgecode };
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "gstack",
"version": "1.28.0.0",
"version": "1.29.1.0",
"description": "Garry's Stack — Claude Code skills + fast headless browser. One repo, one install, entire AI engineering workflow.",
"license": "MIT",
"type": "module",
Expand Down
11 changes: 7 additions & 4 deletions scripts/skill-check.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { discoverTemplates, discoverSkillFiles } from './discover-skills';
import * as fs from 'fs';
import * as path from 'path';
import { execSync } from 'child_process';
import { ALL_HOST_CONFIGS, getExternalHosts, getHostConfig } from '../hosts/index';

const ROOT = path.resolve(import.meta.dir, '..');
const ROOT_REALPATH = fs.realpathSync(ROOT);
Expand Down Expand Up @@ -72,6 +73,12 @@ for (const { tmpl, output } of TEMPLATES) {
console.log(` \u26a0\ufe0f ${output.padEnd(30)} — no template`);
continue;
}
const primaryHost = getHostConfig('claude');
const skillDir = output === 'SKILL.md' ? '.' : output.split('/')[0];
if (primaryHost.generation.skipSkills?.includes(skillDir)) {
console.log(` - ${output.padEnd(30)} — skipped for ${primaryHost.displayName}`);
continue;
}
if (!fs.existsSync(outPath)) {
hasErrors = true;
console.log(` \u274c ${output.padEnd(30)} — generated file missing! Run: bun run gen:skill-docs`);
Expand All @@ -90,8 +97,6 @@ for (const file of SKILL_FILES) {

// ─── External Host Skills (config-driven) ───────────────────

import { getExternalHosts } from '../hosts/index';

for (const hostConfig of getExternalHosts()) {
const hostDir = path.join(ROOT, hostConfig.hostSubdir, 'skills');
if (fs.existsSync(hostDir)) {
Expand Down Expand Up @@ -130,8 +135,6 @@ for (const hostConfig of getExternalHosts()) {

// ─── Freshness (config-driven) ──────────────────────────────

import { ALL_HOST_CONFIGS } from '../hosts/index';

for (const hostConfig of ALL_HOST_CONFIGS) {
const hostFlag = hostConfig.name === 'claude' ? '' : ` --host ${hostConfig.name}`;
console.log(`\n Freshness (${hostConfig.displayName}):`);
Expand Down
Loading