mirror of
https://github.com/garrytan/gstack.git
synced 2026-09-09 06:28:59 +02:00
docs: update project documentation for v1.80.0.0
README troubleshooting + manual uninstall cover the skill ownership gate (.gstack-owned marker, ~/.gstack/backups/skills/<ts>/, foreign same-name skills left untouched). CLAUDE.md and CONTRIBUTING carry the ownership and best-effort Chromium bootstrap invariants for people editing setup and gstack-relink. PROJECT_STRUCTURE gains careful/, freeze/, guard/, unfreeze/, gstack-upgrade/, gstack-relink, and the setup/relink/hook test files. TESTING_INTERNALS documents the anchor-sliced setup harness convention. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
25fe33d447
commit
23fccb982a
@@ -254,6 +254,20 @@ Names are either short (`qa`) or namespaced (`gstack-qa`), controlled by
|
||||
`skill_prefix` in `~/.gstack/config.yaml`. Pass `--no-prefix` or `--prefix` to
|
||||
skip the interactive prompt.
|
||||
|
||||
**Ownership gate (#2119):** `setup` writes a `.gstack-owned` marker into every
|
||||
skill directory it creates, and `setup` (the linker, the alias installer, and
|
||||
both prefix-flip cleanups) and `bin/gstack-relink` only delete or link over an
|
||||
entry they can prove is gstack's. Strong proof (a symlink resolving into gstack,
|
||||
or the marker) allows deleting or refreshing the whole directory. Weak proof (a
|
||||
real SKILL.md byte-identical to the source, or carrying gen-skill-docs' two-line
|
||||
banner) covers only that one file, and a weakly-proven file that differs is
|
||||
moved to `~/.gstack/backups/skills/<ts>/<skill>/SKILL.md` before gstack links
|
||||
over it. Anything else is a foreign skill: skipped, and named in setup's final
|
||||
summary. The rule lives in two copies (`setup` and `bin/gstack-relink`); keep
|
||||
them in sync until the shared helper filed in TODOS.md lands. Pinned by
|
||||
`test/setup-link-ownership.test.ts`, `test/setup-cleanup-orphans.test.ts`, and
|
||||
`test/relink.test.ts`.
|
||||
|
||||
**Note:** Vendoring gstack into a project's repo is deprecated. Use global install
|
||||
+ `./setup --team` instead. See README.md for team mode instructions.
|
||||
|
||||
|
||||
+5
-1
@@ -460,6 +460,8 @@ When Conductor creates a new workspace, `bin/dev-setup` runs automatically. It d
|
||||
- **`.claude/skills/` is gitignored.** The symlinks never get committed.
|
||||
- **Never write raw `ln -snf` in `setup`.** Every link site in `setup` MUST route through the `_link_or_copy SRC DST` helper near the `IS_WINDOWS` detection. The helper preserves `ln -snf` on Unix and switches to `cp -R` / `cp -f` on Windows without Developer Mode, where plain `ln -snf` produces frozen file copies that don't refresh on `git pull`. `test/setup-windows-fallback.test.ts` enforces this with a static invariant — a single raw `ln` call outside the helper body fails CI.
|
||||
- **Synchronous subagent dispatches must state the flag.** Claude Code runs Agent-tool subagents in the background by default (since v2.1.198), so any template step that dispatches a subagent and consumes its output must carry `run_in_background: false`. Use the `{{FOREGROUND_DISPATCH_NOTE}}` placeholder (`scripts/resolvers/constants.ts`) instead of hand-writing the guidance, and add the generated carrier file to `GENERATED_WITH_GUIDANCE` in `test/run-in-background-guidance.test.ts` in the same commit — its structural scanner fails CI on any generated dispatch imperative that lacks the flag.
|
||||
- **Never delete or link over a skill entry `setup` cannot prove is gstack's.** Every destructive site in `setup` (the linker, the alias installer, both prefix-flip cleanups) and in `bin/gstack-relink` goes through the ownership helpers (`_claude_entry_is_ours` / `_claude_entry_owned_strongly` in `setup`, `_entry_is_ours` / `_entry_owned_strongly` in relink). A symlink into gstack or the `.gstack-owned` marker proves the whole directory; a byte-identical or generated-banner SKILL.md proves only that file, and a differing one is moved to `~/.gstack/backups/skills/<ts>/` first. `test/setup-link-ownership.test.ts`, `test/setup-cleanup-orphans.test.ts`, and `test/relink.test.ts` pin it. The rule is duplicated in the two scripts until the shared helper filed in TODOS.md lands: change both.
|
||||
- **`./setup` never fails on Chromium.** The Playwright bootstrap (section `# 2` of `setup`) is best-effort and bounded: every failure becomes a reason code (`skipped`, `chromium-install`, `chromium-install-timeout`, `chromium-install-locked`, `windows-no-node`, `windows-node-modules`, `post-install-launch`) printed in the final summary alongside the browser-dependent skills, and skill registration always runs. `GSTACK_PLAYWRIGHT_INSTALL_TIMEOUT=<seconds>` (default 600) bounds the download; `GSTACK_SKIP_PLAYWRIGHT=1` skips it, the right knob for a no-browser box or a setup-only test loop. Anything you add after the bootstrap must stay independent of the browser. `test/setup-playwright-best-effort.test.ts` pins the block.
|
||||
|
||||
## Testing your changes in a real project
|
||||
|
||||
@@ -513,7 +515,9 @@ cd .claude/skills/gstack && ./setup --no-prefix # switch to /qa, /ship
|
||||
cd .claude/skills/gstack && ./setup --prefix # switch to /gstack-qa, /gstack-ship
|
||||
```
|
||||
|
||||
Setup cleans up the old symlinks automatically. No manual cleanup needed.
|
||||
Setup cleans up the old symlinks automatically. No manual cleanup needed. Only
|
||||
entries gstack created are removed: a skill of your own that shares a name (a
|
||||
hand-written `qa/`, say) is left in place and named in setup's final summary.
|
||||
|
||||
### Alternative: point your global install at a branch
|
||||
|
||||
|
||||
@@ -393,6 +393,11 @@ while IFS= read -r dir; do
|
||||
;;
|
||||
esac
|
||||
done
|
||||
# Directories gstack created carry a .gstack-owned marker (the only signal on
|
||||
# Windows, where installs are file copies with no symlink to read)
|
||||
for marker in ~/.claude/skills/*/.gstack-owned; do
|
||||
[ -f "$marker" ] && rm -rf "$(dirname "$marker")"
|
||||
done
|
||||
# Alias skills install as copies (no symlink to detect) — remove by name
|
||||
rm -rf ~/.claude/skills/_gstack-command ~/.claude/skills/connect-chrome 2>/dev/null
|
||||
|
||||
@@ -540,6 +545,19 @@ install entirely (CI, no-browser boxes); `GSTACK_CHROMIUM_NO_SANDBOX=1` is the
|
||||
fix when Chromium installs but cannot launch because the host blocks
|
||||
unprivileged user namespaces (Ubuntu 24.04+ AppArmor default, #2157).
|
||||
|
||||
**Setup ended with "Not registered (a skill you own already uses the name; left untouched)"?**
|
||||
gstack only deletes or links over a skill entry it can prove it created: a
|
||||
symlink into gstack, a directory carrying the `.gstack-owned` marker `./setup`
|
||||
writes into every directory it creates, or a SKILL.md that is byte-identical to
|
||||
gstack's or carries the generated `<!-- AUTO-GENERATED from ... -->` banner. A
|
||||
`qa/` or `ship/` you wrote yourself is left untouched by `./setup`,
|
||||
`gstack-relink`, and both prefix-mode flips, and the linker names it in the
|
||||
final summary. Rename or move yours, or switch modes (`./setup --prefix` /
|
||||
`--no-prefix`) so the names stop colliding. If you started your own skill from
|
||||
a generated gstack SKILL.md and then edited it, that file is moved to
|
||||
`~/.gstack/backups/skills/<timestamp>/<skill>/SKILL.md` before gstack's is
|
||||
linked in, never deleted.
|
||||
|
||||
**Claude says it can't see the skills?** Make sure your project's `CLAUDE.md` has a gstack section. Add this:
|
||||
|
||||
```
|
||||
|
||||
@@ -36,6 +36,7 @@ gstack/
|
||||
│ ├── fixtures/ # Ground truth JSON, planted-bug fixtures, eval baselines
|
||||
│ ├── skill-validation.test.ts # Tier 1: static validation (free, <1s)
|
||||
│ ├── gen-skill-docs.test.ts # Tier 1: generator quality (free, <1s)
|
||||
│ ├── setup-*.test.ts, relink.test.ts, hook-scripts.test.ts # Tier 1: setup linker ownership + Chromium bootstrap (anchor-sliced from setup), gstack-relink, PreToolUse hooks (free)
|
||||
│ ├── skill-llm-eval.test.ts # Tier 3: LLM-as-judge (~$0.15/run)
|
||||
│ └── skill-e2e-*.test.ts # Tier 2: E2E via claude -p (~$3.85/run, split by category)
|
||||
├── qa-only/ # /qa-only skill (report-only QA, no fixes)
|
||||
@@ -54,7 +55,11 @@ gstack/
|
||||
├── investigate/ # /investigate skill (systematic root-cause debugging)
|
||||
├── spec/ # /spec skill (five-phase spec → GitHub issue, optional agent spawn, /ship auto-closes)
|
||||
├── retro/ # Retrospective skill (includes /retro global cross-project mode)
|
||||
├── bin/ # CLI utilities (gstack-repo-mode, gstack-slug, gstack-config, gstack-wtree, gstack-evidence, gstack-issue-guard, etc.)
|
||||
├── careful/ # /careful skill; bin/check-careful.sh (PreToolUse destructive-command hook) + bin/hook-extract.sh (shared hook helpers: payload extraction, deny JSON, gstack_hook_state_root)
|
||||
├── freeze/ # /freeze skill; bin/check-freeze.sh (PreToolUse edit-boundary hook; sources careful/bin/hook-extract.sh, fails closed)
|
||||
├── guard/, unfreeze/ # /guard (careful + freeze in one), /unfreeze
|
||||
├── gstack-upgrade/ # /gstack-upgrade skill + migrations/ (run after ./setup during an upgrade)
|
||||
├── bin/ # CLI utilities (gstack-repo-mode, gstack-slug, gstack-config, gstack-wtree, gstack-evidence, gstack-issue-guard, gstack-relink, etc.)
|
||||
├── document-release/ # /document-release skill (post-ship doc updates + Diataxis coverage map)
|
||||
├── document-generate/ # /document-generate skill (Diataxis doc generator: tutorial/how-to/reference/explanation)
|
||||
├── cso/ # /cso skill (OWASP Top 10 + STRIDE security audit)
|
||||
@@ -70,14 +75,14 @@ gstack/
|
||||
├── extension/ # Chrome extension (side panel + activity feed + CSS inspector)
|
||||
├── lib/ # Shared libraries (worktree.ts, egress-receipt.ts, context-bill.ts, redact-engine.ts, tracker-guard.ts, version-source.ts, code-intelligence/)
|
||||
├── patches/ # bun `patchedDependencies` patches (playwright-core windowsHide)
|
||||
├── docs/designs/ # Design documents
|
||||
├── docs/designs/ # Design documents (incl. fork-port-residual-2026-09/ evaluation evidence)
|
||||
├── setup-deploy/ # /setup-deploy skill (one-time deploy config)
|
||||
├── .github/ # CI workflows + shared composite actions (.github/actions/) + Docker image (claude CLI pinned)
|
||||
│ ├── workflows/ # evals.yml (E2E on Ubicloud), quality-gate.yml (secret scan), dependency-review.yml, osv-scanner.yml, skill-docs.yml, actionlint.yml, and 8 more (windows, periodic evals, release gates, ci-image)
|
||||
│ └── docker/ # Dockerfile.ci (pre-baked toolchain + Playwright/Chromium)
|
||||
├── contrib/ # Contributor-only tools (never installed for users)
|
||||
│ └── add-host/ # /gstack-contrib-add-host skill
|
||||
├── setup # One-time setup: build binary + symlink skills
|
||||
├── setup # One-time setup: build binary + best-effort Chromium bootstrap + link skills (ownership-gated)
|
||||
├── SKILL.md # Generated from SKILL.md.tmpl (don't edit directly)
|
||||
├── SKILL.md.tmpl # Template: edit this, run gen:skill-docs
|
||||
├── ETHOS.md # Builder philosophy (Boil the Ocean, Search Before Building)
|
||||
|
||||
@@ -122,6 +122,26 @@ in the test trees must carry a `timeout`, enforced by
|
||||
`test/spawnsync-timeout-tripwire.test.ts` with a shrink-only exemption
|
||||
ratchet.
|
||||
|
||||
**Anchor-sliced `setup` harnesses.** `setup` is one large bash script, so the
|
||||
free tests that pin its linker, cleanup, and Chromium-bootstrap behavior never
|
||||
run the whole thing. They slice the source by anchor (`extractFn(name)` takes
|
||||
`name() {` through the next `\n}\n`; `test/setup-playwright-best-effort.test.ts`
|
||||
slices the `# 2. Ensure Playwright's Chromium is available` block up to
|
||||
`# 2b.`), join the extracted functions with stubbed collaborators, and execute
|
||||
the REAL bash under a temp `HOME` with stubbed probes and installers. Two rules
|
||||
keep the harness honest: renaming a function or anchor comment in `setup` fails
|
||||
the test with `function not found` / `anchor not found` instead of silently
|
||||
testing nothing, and `test/setup-link-ownership.test.ts` and
|
||||
`test/setup-playwright-best-effort.test.ts` throw on any `command not found` on
|
||||
stderr as harness drift (a helper the test forgot to extract) rather than
|
||||
letting it degrade into a pass. Files: `test/setup-link-ownership.test.ts`,
|
||||
`test/setup-cleanup-orphans.test.ts`, `test/setup-playwright-best-effort.test.ts`.
|
||||
`test/relink.test.ts` shells out to a copy of the real `bin/gstack-relink`
|
||||
against a temp `GSTACK_INSTALL_DIR` / `GSTACK_SKILLS_DIR`, and
|
||||
`test/hook-scripts.test.ts` runs the real `careful/bin/check-careful.sh` and
|
||||
`freeze/bin/check-freeze.sh` with JSON payloads on stdin (including the
|
||||
`GSTACK_HOME` state-root parity against `bin/gstack-paths`).
|
||||
|
||||
## Cloud sandboxes (Vercel / Conductor cloud workspaces)
|
||||
|
||||
Syscall-supervised sandboxes need environment setup before `bun run test` can
|
||||
|
||||
Reference in New Issue
Block a user