diff --git a/.github/workflows/actionlint.yml b/.github/workflows/actionlint.yml index 544f694fd6..e2160473c1 100644 --- a/.github/workflows/actionlint.yml +++ b/.github/workflows/actionlint.yml @@ -20,7 +20,7 @@ permissions: jobs: actionlint: - runs-on: ubicloud-standard-2 + runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 with: diff --git a/.github/workflows/ci-image.yml b/.github/workflows/ci-image.yml index 4cb1dccc27..3c3c682143 100644 --- a/.github/workflows/ci-image.yml +++ b/.github/workflows/ci-image.yml @@ -15,7 +15,7 @@ on: jobs: build: - runs-on: ubicloud-standard-8 + runs-on: ubuntu-latest permissions: contents: read packages: write diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index b600ada817..6ae31cfafb 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -18,7 +18,7 @@ permissions: jobs: dependency-review: - runs-on: ubicloud-standard-8 + runs-on: ubuntu-latest timeout-minutes: 10 permissions: contents: read diff --git a/.github/workflows/evals-periodic.yml b/.github/workflows/evals-periodic.yml index 3017868b9c..fd42e80c76 100644 --- a/.github/workflows/evals-periodic.yml +++ b/.github/workflows/evals-periodic.yml @@ -15,7 +15,7 @@ env: jobs: build-image: - runs-on: ubicloud-standard-8 + runs-on: ubuntu-latest permissions: contents: read packages: write @@ -66,7 +66,7 @@ jobs: ${{ env.IMAGE }}:latest evals: - runs-on: ubicloud-standard-8 + runs-on: ubuntu-latest needs: build-image container: image: ${{ needs.build-image.outputs.image-tag }} diff --git a/.github/workflows/evals.yml b/.github/workflows/evals.yml index 2a5916dbe0..4c6d23a6b1 100644 --- a/.github/workflows/evals.yml +++ b/.github/workflows/evals.yml @@ -21,7 +21,7 @@ jobs: # needs-chain tolerates it because no eval test selects on a lockfile-only # diff — a maintainer's next push rebuilds the image with real perms. if: github.actor != 'dependabot[bot]' - runs-on: ubicloud-standard-8 + runs-on: ubuntu-latest permissions: contents: read packages: write @@ -86,7 +86,7 @@ jobs: # Same-repo PRs, pushes, and workflow_dispatch keep full coverage. Fork work # gets real coverage via a trusted base-repo branch. evals: - runs-on: ${{ matrix.suite.runner || 'ubicloud-standard-8' }} + runs-on: ${{ matrix.suite.runner || 'ubuntu-latest' }} needs: build-image if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository container: @@ -104,7 +104,7 @@ jobs: file: test/skill-llm-eval.test.ts - name: e2e-browse file: test/skill-e2e-bws.test.ts - runner: ubicloud-standard-8 + runner: ubuntu-latest - name: e2e-plan file: test/skill-e2e-plan.test.ts - name: e2e-deploy @@ -348,7 +348,7 @@ jobs: retention-days: 90 report: - runs-on: ubicloud-standard-2 + runs-on: ubuntu-latest needs: evals if: always() && github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository timeout-minutes: 5 @@ -418,7 +418,7 @@ jobs: $(echo -e "$SUITE_LINES") --- - *ubicloud-standard-8 runners (Docker: pre-baked toolchain + deps) | wall clock ≈ slowest suite*" + *ubuntu-latest runners (Docker: pre-baked toolchain + deps) | wall clock ≈ slowest suite*" if [ "$FAILED" -gt 0 ]; then FAILURES="" diff --git a/.github/workflows/free-tests.yml b/.github/workflows/free-tests.yml index 7827277726..c1754cb076 100644 --- a/.github/workflows/free-tests.yml +++ b/.github/workflows/free-tests.yml @@ -45,7 +45,7 @@ permissions: jobs: free-tests: - runs-on: ubicloud-standard-8 + runs-on: ubuntu-latest timeout-minutes: 20 steps: - uses: actions/checkout@v7 diff --git a/.github/workflows/make-pdf-gate.yml b/.github/workflows/make-pdf-gate.yml index fa98082764..27a493de9a 100644 --- a/.github/workflows/make-pdf-gate.yml +++ b/.github/workflows/make-pdf-gate.yml @@ -54,13 +54,13 @@ jobs: run: brew install poppler - name: Install poppler-utils (Ubuntu) - if: matrix.os == 'ubicloud-standard-8' + if: matrix.os == 'ubuntu-latest' run: sudo apt-get update && sudo apt-get install -y poppler-utils # Install a color-emoji font BEFORE Chromium launches so the emoji render # gate has a fallback font. macOS ships Apple Color Emoji already. - name: Install color-emoji font (Ubuntu) - if: matrix.os == 'ubicloud-standard-8' + if: matrix.os == 'ubuntu-latest' run: | sudo apt-get install -y fonts-noto-color-emoji fc-cache -f || true diff --git a/.github/workflows/pr-title-sync.yml b/.github/workflows/pr-title-sync.yml index 5a01ae2754..cc76102e93 100644 --- a/.github/workflows/pr-title-sync.yml +++ b/.github/workflows/pr-title-sync.yml @@ -31,7 +31,7 @@ concurrency: jobs: sync: name: Sync PR title to VERSION - runs-on: ubicloud-standard-2 + runs-on: ubuntu-latest permissions: contents: read pull-requests: write diff --git a/.github/workflows/quality-gate.yml b/.github/workflows/quality-gate.yml index 5760d1a6b0..6088e1c182 100644 --- a/.github/workflows/quality-gate.yml +++ b/.github/workflows/quality-gate.yml @@ -28,7 +28,7 @@ concurrency: jobs: quality: - runs-on: ubicloud-standard-8 + runs-on: ubuntu-latest timeout-minutes: 20 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v4 diff --git a/.github/workflows/skill-docs.yml b/.github/workflows/skill-docs.yml index 47ba5f36bf..aed84e32d4 100644 --- a/.github/workflows/skill-docs.yml +++ b/.github/workflows/skill-docs.yml @@ -15,7 +15,7 @@ concurrency: jobs: check-freshness: - runs-on: ubicloud-standard-2 + runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 - uses: oven-sh/setup-bun@v2 diff --git a/.github/workflows/version-gate.yml b/.github/workflows/version-gate.yml index 00a2e25ecd..f07108dd9e 100644 --- a/.github/workflows/version-gate.yml +++ b/.github/workflows/version-gate.yml @@ -14,7 +14,7 @@ concurrency: jobs: check: name: Check VERSION is not stale vs queue - runs-on: ubicloud-standard-2 + runs-on: ubuntu-latest permissions: contents: read pull-requests: read diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 77e188ae8a..006efaf9ee 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -9,7 +9,7 @@ gstack skills are Markdown files that Claude Code discovers from a `skills/` dir That's what dev mode does. It symlinks your repo into the local `.claude/skills/` directory so Claude Code reads skills straight from your checkout. ```bash -git clone https://github.com/garrytan/gstack.git && cd gstack +git clone https://github.com/seamfix/gstack.git && cd gstack bun install # install dependencies bin/dev-setup # activate dev mode ``` diff --git a/README.md b/README.md index 7b2b9305ea..3830ea7bc7 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ Fork it. Improve it. Make it yours. And if you want to hate on free open source Open Claude Code and paste this. Claude does the rest. -> Install gstack: run **`git clone --single-branch --depth 1 https://github.com/garrytan/gstack.git ~/.claude/skills/gstack && cd ~/.claude/skills/gstack && ./setup`** then add a "gstack" section to CLAUDE.md that says to use the /browse skill from gstack for all web browsing, never use mcp\_\_claude-in-chrome\_\_\* tools, and lists the available skills: /office-hours, /plan-ceo-review, /plan-eng-review, /plan-design-review, /design-consultation, /design-shotgun, /design-html, /review, /ship, /land-and-deploy, /canary, /benchmark, /browse, /connect-chrome, /qa, /qa-only, /design-review, /setup-browser-cookies, /setup-deploy, /setup-gbrain, /retro, /investigate, /document-release, /document-generate, /codex, /cso, /autoplan, /plan-devex-review, /devex-review, /careful, /freeze, /guard, /unfreeze, /gstack-upgrade, /learn. Then ask the user if they also want to add gstack to the current project so teammates get it. +> Install gstack: run **`git clone --single-branch --depth 1 https://github.com/seamfix/gstack.git ~/.claude/skills/gstack && cd ~/.claude/skills/gstack && ./setup`** then add a "gstack" section to CLAUDE.md that says to use the /browse skill from gstack for all web browsing, never use mcp\_\_claude-in-chrome\_\_\* tools, and lists the available skills: /office-hours, /plan-ceo-review, /plan-eng-review, /plan-design-review, /design-consultation, /design-shotgun, /design-html, /review, /ship, /land-and-deploy, /canary, /benchmark, /browse, /connect-chrome, /qa, /qa-only, /design-review, /setup-browser-cookies, /setup-deploy, /setup-gbrain, /retro, /investigate, /document-release, /document-generate, /codex, /cso, /autoplan, /plan-devex-review, /devex-review, /careful, /freeze, /guard, /unfreeze, /gstack-upgrade, /learn. Then ask the user if they also want to add gstack to the current project so teammates get it. ### Step 2: Team mode — auto-update for shared repos (recommended) @@ -67,7 +67,7 @@ Swap `required` for `optional` if you'd rather nudge teammates than block them. OpenClaw spawns Claude Code sessions via ACP, so every gstack skill just works when Claude Code has gstack installed. Paste this to your OpenClaw agent: -> Install gstack: run `git clone --single-branch --depth 1 https://github.com/garrytan/gstack.git ~/.claude/skills/gstack && cd ~/.claude/skills/gstack && ./setup` to install gstack for Claude Code. Then add a "Coding Tasks" section to AGENTS.md that says: when spawning Claude Code sessions for coding work, tell the session to use gstack skills. Include these examples — security audit: "Load gstack. Run /cso", code review: "Load gstack. Run /review", QA test a URL: "Load gstack. Run /qa https://...", build a feature end-to-end: "Load gstack. Run /autoplan, implement the plan, then run /ship", plan before building: "Load gstack. Run /office-hours then /autoplan. Save the plan, don't implement." +> Install gstack: run `git clone --single-branch --depth 1 https://github.com/seamfix/gstack.git ~/.claude/skills/gstack && cd ~/.claude/skills/gstack && ./setup` to install gstack for Claude Code. Then add a "Coding Tasks" section to AGENTS.md that says: when spawning Claude Code sessions for coding work, tell the session to use gstack skills. Include these examples — security audit: "Load gstack. Run /cso", code review: "Load gstack. Run /review", QA test a URL: "Load gstack. Run /qa https://...", build a feature end-to-end: "Load gstack. Run /autoplan, implement the plan, then run /ship", plan before building: "Load gstack. Run /office-hours then /autoplan. Save the plan, don't implement." **After setup, just talk to your OpenClaw agent naturally:** @@ -105,7 +105,7 @@ gstack works on 10 AI coding agents, not just Claude. Setup auto-detects which agents you have installed: ```bash -git clone --single-branch --depth 1 https://github.com/garrytan/gstack.git ~/gstack +git clone --single-branch --depth 1 https://github.com/seamfix/gstack.git ~/gstack cd ~/gstack && ./setup ``` diff --git a/TODOS.md b/TODOS.md index 742ecf796e..565f1b50f7 100644 --- a/TODOS.md +++ b/TODOS.md @@ -1,5 +1,21 @@ # TODOS +## FORK-LOCAL (seamfix) + +### Enable this fork's own CI workflows (Platform, repo settings) + +**What:** The fork ships 14 workflow files but only CodeQL and Dependabot are active; `free-tests`, `quality-gate`, +`version-gate` and `skill-docs` never run on fork-local PRs (seamfix/gstack#2 review, 2026-09-08). `skill-docs` is the +gate that catches template/generated drift. +**Why:** Fork-local patches are otherwise verified only on the author's machine. +**Effort:** S (repo settings). **Priority:** P2. **Owner:** Platform Engineers (admin). + +### Fork-local changes that every upstream sync must re-apply + +`bin/gstack-team-init`, `bin/gstack-update-check`, `gstack-upgrade/SKILL.md(.tmpl)`, README/CONTRIBUTING/tutorial clone +commands → `seamfix/gstack`; `.github/workflows/*.yml` `runs-on: ubuntu-latest` (upstream uses Ubicloud runners this org doesn't have — jobs queue forever). Guarded by the `fork-local:` test in `test/team-mode.test.ts` — if it fails after a sync, +re-apply. Canonical list: seamfix/ai-governance `docs/runbooks/gstack-fork-sync.md`. + ## NEXT PRIORITY ### P1: ZeroEntropy sunset — gbrain's default embedding provider dies Sept 4, 2026 (#2365) diff --git a/bin/gstack-team-init b/bin/gstack-team-init index 99538425f2..64b7f64b0a 100755 --- a/bin/gstack-team-init +++ b/bin/gstack-team-init @@ -54,11 +54,11 @@ fi if [ "$MODE" = "optional" ]; then SNIPPET='## gstack (recommended) -This project uses [gstack](https://github.com/garrytan/gstack) for AI-assisted workflows. +This project uses [gstack](https://github.com/seamfix/gstack) for AI-assisted workflows. Install it for the best experience: ```bash -git clone --depth 1 https://github.com/garrytan/gstack.git ~/.claude/skills/gstack +git clone --depth 1 https://github.com/seamfix/gstack.git ~/.claude/skills/gstack cd ~/.claude/skills/gstack && ./setup --team ``` @@ -82,7 +82,7 @@ If GSTACK_MISSING: STOP. Do not proceed. Tell the user: > gstack is required for all AI-assisted work in this repo. > Install it: > ```bash -> git clone --depth 1 https://github.com/garrytan/gstack.git ~/.claude/skills/gstack +> git clone --depth 1 https://github.com/seamfix/gstack.git ~/.claude/skills/gstack > cd ~/.claude/skills/gstack && ./setup --team > ``` > Then restart your AI coding tool. @@ -136,7 +136,7 @@ BLOCKED: gstack is not installed globally. gstack is required for AI-assisted work in this repo. Install it: - git clone --depth 1 https://github.com/garrytan/gstack.git ~/.claude/skills/gstack + git clone --depth 1 https://github.com/seamfix/gstack.git ~/.claude/skills/gstack cd ~/.claude/skills/gstack && ./setup --team Then restart your AI coding tool. @@ -202,5 +202,5 @@ if [ ${#GENERATED[@]} -gt 0 ]; then fi echo "" echo "Each developer then runs:" -echo " git clone --depth 1 https://github.com/garrytan/gstack.git ~/.claude/skills/gstack" +echo " git clone --depth 1 https://github.com/seamfix/gstack.git ~/.claude/skills/gstack" echo " cd ~/.claude/skills/gstack && ./setup --team" diff --git a/bin/gstack-update-check b/bin/gstack-update-check index 3573c7878a..ee51559a47 100755 --- a/bin/gstack-update-check +++ b/bin/gstack-update-check @@ -50,8 +50,17 @@ CACHE_FILE="$STATE_DIR/last-update-check" MARKER_FILE="$STATE_DIR/just-upgraded-from" SNOOZE_FILE="$STATE_DIR/update-snoozed" VERSION_FILE="$GSTACK_DIR/VERSION" -REMOTE_URL="${GSTACK_REMOTE_URL:-https://raw.githubusercontent.com/garrytan/gstack/main/VERSION}" -REMOTE_REPO="${GSTACK_REMOTE_REPO:-https://github.com/garrytan/gstack.git}" +# Fork-local: compare against the install's OWN origin (seamfix/gstack), never a hardcoded upstream — otherwise every +# fork install is nagged toward upstream forever (fork 1.68.3 vs upstream 1.81.0) while auto-upgrade pulls the fork. +gstack_origin_repo() { # owner/repo of the install's own origin (https or ssh, with or without .git); empty if not GitHub + local u; u="$(git -C "${GSTACK_DIR:-.}" remote get-url origin 2>/dev/null || true)" + u="${u%/}"; u="${u%.git}" + printf '%s' "$u" | sed -nE 's#^.*github\.com[:/]([^/]+/[^/]+)$#\1#p' +} +_ORIGIN_REPO="$(gstack_origin_repo)" +[ -n "$_ORIGIN_REPO" ] || _ORIGIN_REPO="seamfix/gstack" +REMOTE_URL="${GSTACK_REMOTE_URL:-https://raw.githubusercontent.com/$_ORIGIN_REPO/main/VERSION}" +REMOTE_REPO="${GSTACK_REMOTE_REPO:-https://github.com/$_ORIGIN_REPO.git}" # ─── Force flag (busts cache + snooze for standalone /gstack-upgrade) ── if [ "${1:-}" = "--force" ]; then @@ -237,7 +246,7 @@ if [ -z "${GSTACK_REMOTE_URL:-}" ]; then git ls-remote "$1" refs/heads/main 2>/dev/null' _ "$REMOTE_REPO" || true)" _REMOTE_SHA="$(echo "$_LSR_LINE" | awk '{print $1}')" if echo "$_REMOTE_SHA" | grep -qE '^[0-9a-f]{40}$'; then - _SHA_URL="https://raw.githubusercontent.com/garrytan/gstack/${_REMOTE_SHA}/VERSION" + _SHA_URL="https://raw.githubusercontent.com/$_ORIGIN_REPO/${_REMOTE_SHA}/VERSION" REMOTE="$(_receipted_version_fetch "$_SHA_URL")" fi fi diff --git a/docs/tutorial-document-generate.md b/docs/tutorial-document-generate.md index 7e8e78f452..8f3441d992 100644 --- a/docs/tutorial-document-generate.md +++ b/docs/tutorial-document-generate.md @@ -4,7 +4,7 @@ You'll run `/document-generate` against a project you already have, watch it wri ## What you'll need -- gstack installed (`git clone --single-branch --depth 1 https://github.com/garrytan/gstack.git ~/.claude/skills/gstack && cd ~/.claude/skills/gstack && ./setup`) +- gstack installed (`git clone --single-branch --depth 1 https://github.com/seamfix/gstack.git ~/.claude/skills/gstack && cd ~/.claude/skills/gstack && ./setup`) - Claude Code running in any project that has at least one piece of public surface (a CLI command, an exported function, a config option, a skill, an API endpoint) - About 90 seconds diff --git a/gstack-upgrade/SKILL.md b/gstack-upgrade/SKILL.md index b5ec03d428..45a97ecf74 100644 --- a/gstack-upgrade/SKILL.md +++ b/gstack-upgrade/SKILL.md @@ -171,7 +171,7 @@ If `$STASH_OUTPUT` contains "Saved working directory", warn the user: "Note: loc ```bash PARENT=$(dirname "$INSTALL_DIR") TMP_DIR=$(mktemp -d) -git clone --depth 1 https://github.com/garrytan/gstack.git "$TMP_DIR/gstack" +git clone --depth 1 https://github.com/seamfix/gstack.git "$TMP_DIR/gstack" mv "$INSTALL_DIR" "$INSTALL_DIR.bak" mv "$TMP_DIR/gstack" "$INSTALL_DIR" cd "$INSTALL_DIR" && ./setup diff --git a/gstack-upgrade/SKILL.md.tmpl b/gstack-upgrade/SKILL.md.tmpl index ca211d919a..0a4def2f14 100644 --- a/gstack-upgrade/SKILL.md.tmpl +++ b/gstack-upgrade/SKILL.md.tmpl @@ -168,7 +168,7 @@ If `$STASH_OUTPUT` contains "Saved working directory", warn the user: "Note: loc ```bash PARENT=$(dirname "$INSTALL_DIR") TMP_DIR=$(mktemp -d) -git clone --depth 1 https://github.com/garrytan/gstack.git "$TMP_DIR/gstack" +git clone --depth 1 https://github.com/seamfix/gstack.git "$TMP_DIR/gstack" mv "$INSTALL_DIR" "$INSTALL_DIR.bak" mv "$TMP_DIR/gstack" "$INSTALL_DIR" cd "$INSTALL_DIR" && {{SETUP_COMMAND}} diff --git a/test/team-mode.test.ts b/test/team-mode.test.ts index ce8c1d6107..d47f0651fb 100644 --- a/test/team-mode.test.ts +++ b/test/team-mode.test.ts @@ -199,6 +199,42 @@ describe('gstack-team-init', () => { fs.rmSync(tmpDir, { recursive: true, force: true }); }); + test('fork-local: install and upgrade paths point at seamfix/gstack, never upstream', () => { + // Guards the fork-local patch (seamfix/gstack#2). An upstream sync reverts these silently; this test is the tripwire. + run(`${TEAM_INIT} required`, { cwd: tmpDir }); + const claude = fs.readFileSync(path.join(tmpDir, 'CLAUDE.md'), 'utf-8'); + expect(claude).toContain('https://github.com/seamfix/gstack.git'); + expect(claude).not.toContain('garrytan/gstack'); + for (const rel of ['bin/gstack-team-init', 'bin/gstack-update-check', 'gstack-upgrade/SKILL.md.tmpl', 'gstack-upgrade/SKILL.md']) { + const src = fs.readFileSync(path.join(ROOT, rel), 'utf-8'); + expect(src, `${rel} still references upstream`).not.toContain('github.com/garrytan/gstack'); + } + const readme = fs.readFileSync(path.join(ROOT, 'README.md'), 'utf-8'); + expect(readme).not.toMatch(/git clone[^\n]*garrytan\/gstack\.git/); + // Workflows must run on GitHub-hosted runners: the seamfix org has no Ubicloud runners, so upstream's labels queue forever. + for (const wf of fs.readdirSync(path.join(ROOT, '.github', 'workflows')).filter(f => f.endsWith('.yml'))) { + const y = fs.readFileSync(path.join(ROOT, '.github', 'workflows', wf), 'utf-8'); + expect(y, `${wf} requests a ubicloud runner`).not.toMatch(/runs-on:\s*ubicloud/); + } + }); + + test('fork-local: update-check derives owner/repo from the install origin (https, https+.git, ssh)', () => { + // Extract the function into a file and source it — no quoting through two shells. + const fnFile = path.join(tmpDir, 'origin-fn.sh'); + execSync(`sed -n '/^gstack_origin_repo()/,/^}/p' bin/gstack-update-check > '${fnFile}'`, { cwd: ROOT, shell: '/bin/bash' }); + expect(fs.readFileSync(fnFile, 'utf-8')).toContain('gstack_origin_repo()'); + for (const [url, want] of [ + ['https://github.com/seamfix/gstack.git', 'seamfix/gstack'], + ['https://github.com/seamfix/gstack', 'seamfix/gstack'], + ['git@github.com:seamfix/gstack.git', 'seamfix/gstack'], + ['https://github.com/garrytan/gstack.git', 'garrytan/gstack'], + ]) { + execSync(`git remote remove origin 2>/dev/null; git remote add origin '${url}'`, { cwd: tmpDir, shell: '/bin/bash' }); + const out = run(`bash -c 'source "${fnFile}"; gstack_origin_repo'`, { env: { GSTACK_DIR: tmpDir } }); + expect(out.stdout.trim(), url).toBe(want); + } + }); + test('errors without a mode argument', () => { const result = run(TEAM_INIT, { cwd: tmpDir }); expect(result.exitCode).not.toBe(0);