mirror of
https://github.com/garrytan/gstack.git
synced 2026-09-09 14:38:59 +02:00
docs: sync project documentation with the v1.67.0.0 fix wave
Port range 10000-49151 + busy-vs-dead daemon semantics + XProtect launch heal + browse-daemon.log in BROWSER.md/ARCHITECTURE.md; #2557 dead security surface (shield, L4b Haiku, DeBERTa ensemble, canary injector) marked removed in README/ARCHITECTURE per CLAUDE.md's do-not-redocument note; runtime-asset installs + alias copies in CONTRIBUTING/CLAUDE.md; manual uninstall fixed for asset-bearing dirs, alias copies, cursor/opencode roots, and the timeline Stop hook; gbrain-refresh out-dir render path; npm-valid package.json version translation documented in CLAUDE.md; patches/ in the project tree; two CHANGELOG accuracy fixes (-272 net lines, upgrade-time quarantine-clear) + release-summary em-dash polish. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
730327e45f
commit
8cc379b872
+6
-6
@@ -69,11 +69,11 @@ The server writes `.gstack/browse.json` (atomic write via tmp + rename, mode 0o6
|
||||
{ "pid": 12345, "port": 34567, "token": "uuid-v4", "startedAt": "...", "binaryVersion": "abc123" }
|
||||
```
|
||||
|
||||
The CLI reads this file to find the server. If the file is missing or the server fails an HTTP health check, the CLI spawns a new server. On Windows, PID-based process detection is unreliable in Bun binaries, so the health check (GET /health) is the primary liveness signal on all platforms.
|
||||
The CLI reads this file to find the server. If the file is missing or the daemon process is dead, the CLI spawns a new server. A process that is alive but not answering `/health` is busy, not dead: the CLI probes for a bounded ~8s, then reports busy with a nonzero exit — only an explicit `--force-restart` kills a live daemon. Process liveness uses signal-0 (`isProcessAlive`, EPERM counts as alive) on every platform, with the health check (GET /health) as the responsiveness signal. Daemon stdout/stderr persists to `<project>/.gstack/browse-daemon.log`.
|
||||
|
||||
### Port selection
|
||||
|
||||
Random port between 10000-60000 (retry up to 5 on collision). This means 10 Conductor workspaces can each run their own browse daemon with zero configuration and zero port conflicts. The old approach (scanning 9400-9409) broke constantly in multi-workspace setups.
|
||||
Random port between 10000-49151 (retry up to 5 on collision), allocated through the shared `browse/src/port-allocator.ts` so every long-lived gstack listener draws from the same range. The range ends at 49151 on purpose: 49152-65535 is the macOS ephemeral pool, and allocating inside it meant the OS could hand the same port to another process moments later. This means 10 Conductor workspaces can each run their own browse daemon with zero configuration and zero port conflicts. The old approach (scanning 9400-9409) broke constantly in multi-workspace setups.
|
||||
|
||||
### Version auto-restart
|
||||
|
||||
@@ -176,17 +176,17 @@ The Chrome sidebar agent has tools (Bash, Read, Glob, Grep, WebFetch) and reads
|
||||
|
||||
1. **L1-L3 content security (`browse/src/content-security.ts`).** Runs on every page-content command and every tool output: datamarking, hidden-element strip, ARIA regex, URL blocklist, and a trust-boundary envelope wrapper. Applied at both the server and the agent.
|
||||
|
||||
2. **L4 ML classifier — TestSavantAI (`browse/src/security-classifier.ts`).** A 22MB BERT-small ONNX model (int8 quantized) bundled with the agent. Runs locally, no network. Scans every user message and every Read/Glob/Grep/WebFetch tool output before Claude sees it. Opt-in 721MB DeBERTa-v3 ensemble via `GSTACK_SECURITY_ENSEMBLE=deberta`.
|
||||
2. **L4 ML classifier — TestSavantAI (`browse/src/security-classifier.ts`).** A 22MB BERT-small ONNX model (int8 quantized) running in the security sidecar subprocess. Runs locally, no network. Scans page-derived content on the inject-scan path before the agent sees it.
|
||||
|
||||
3. **L4b transcript classifier.** A Claude Haiku pass that looks at the full conversation shape (user message, tool calls, tool output), not just text. Gated by `LOG_ONLY: 0.40` so most clean traffic skips the paid call.
|
||||
3. **L4b transcript classifier (removed).** A Claude Haiku conversation-shape pass existed until the chat-path agent that invoked it was ripped; it was deleted as dead code (zero production callers), along with the opt-in DeBERTa ensemble. Do not re-document either as live.
|
||||
|
||||
4. **L5 canary token (`browse/src/security.ts`).** A random token injected into the system prompt at session start. Rolling-buffer detection across `text_delta` and `input_json_delta` streams catches the token if it shows up anywhere in Claude's output, tool arguments, URLs, or file writes. Deterministic BLOCK — if the token leaks, the attacker convinced Claude to reveal the system prompt, and the session ends.
|
||||
4. **L5 canary token (`browse/src/security.ts`).** Generate/inject/detect utilities for a random system-prompt token whose leak means the attacker convinced the model to reveal the system prompt. Canary leak BLOCKs deterministically. The utilities are pure and tested; the chat prompt-builder that injected the canary was ripped, so no production path injects it today.
|
||||
|
||||
5. **L6 ensemble combiner (`combineVerdict`).** BLOCK requires agreement from two ML classifiers at >= `WARN` (0.75), not a single confident hit. This is the Stack Overflow instruction-writing false-positive mitigation. On tool-output scans, single-layer high confidence BLOCKs directly — the content wasn't user-authored, so the FP concern doesn't apply.
|
||||
|
||||
**Critical constraint:** `security-classifier.ts` runs only in the security sidecar subprocess (`security-sidecar-entry.ts`), never in the compiled browse binary. `@huggingface/transformers` v4 requires `onnxruntime-node`, which fails `dlopen` from Bun compile's temp extract directory. Only the pure-string pieces (canary inject/check, verdict combiner) are in `security.ts`, which is safe to import from `server.ts`. (The attack log lives in `tunnel-denial-log.ts`; the session-state/status surface was removed in #2557.)
|
||||
|
||||
**Env knobs:** `GSTACK_SECURITY_OFF=1` is a real kill switch (skips ML scan, canary still injects). Model cache at `~/.gstack/models/testsavant-small/` (112MB, first run) and `~/.gstack/models/deberta-v3-injection/` (721MB, opt-in only). Attack log at `~/.gstack/security/attempts.jsonl` (salted sha256 + domain, rotates at 10MB, 5 generations). Per-device salt at `~/.gstack/security/device-salt` (0600), cached in-process to survive FS-unwritable environments.
|
||||
**Env knobs:** `GSTACK_SECURITY_OFF=1` is a real kill switch (classifier stays off even if warmed; the L1-L3 filters keep running). Model cache at `~/.gstack/models/testsavant-small/` (112MB, first run). Attack log at `~/.gstack/security/attempts.jsonl` (salted sha256 + domain, rotates at 10MB, 5 generations). Per-device salt at `~/.gstack/security/device-salt` (0600), cached in-process to survive FS-unwritable environments.
|
||||
|
||||
**Visibility.** A centered banner appears on canary leak or BLOCK verdict with the exact layer scores. `bin/gstack-security-dashboard` aggregates local attempts; `supabase/functions/community-pulse` aggregates opt-in community telemetry across users. (The sidebar header's SEC shield icon and the `/health` `security` field were removed in #2557: their only data source — `~/.gstack/security/session-state.json` — lost its only writer when the chat-path agent was ripped, so the shield reported stale or empty state. The live defenses report through their own call sites.)
|
||||
|
||||
|
||||
+35
-8
@@ -168,8 +168,18 @@ for the full design + decision trail.
|
||||
1. **First call.** CLI checks `<project>/.gstack/browse.json` for a running
|
||||
server. None found — it spawns `bun run browse/src/server.ts` in the
|
||||
background. Daemon launches headless Chromium via Playwright, picks a
|
||||
random port (10000–60000), generates a bearer token, writes the state
|
||||
file (chmod 600), starts accepting requests. ~3 seconds.
|
||||
random port (10000–49151, deliberately below the macOS ephemeral pool
|
||||
49152-65535 so the OS never hands a colliding port to another process),
|
||||
generates a bearer token, writes the state file (chmod 600), starts
|
||||
accepting requests. ~3 seconds. One launch-time exception to fail-fast:
|
||||
when a macOS XProtect definition update SIGKILLs the pinned Chromium at
|
||||
spawn, the daemon classifies the kill signature, clears the quarantine
|
||||
flag on the Playwright cache, reinstalls the pinned revision from the
|
||||
gstack install root (bounded ~120s), and retries once — at most once per
|
||||
daemon process. If the heal can't complete, the original launch error
|
||||
plus manual `bunx playwright install chromium` guidance lands on daemon
|
||||
stderr (see `browse-daemon.log`). Wired at all three launch sites in
|
||||
`browser-manager.ts` via `browse/src/xprotect-heal.ts`.
|
||||
2. **Subsequent calls.** CLI reads the state file, sends an HTTP POST with
|
||||
the bearer token, prints the response. ~100-200ms round trip.
|
||||
3. **Idle shutdown.** After 30 minutes of no commands, daemon shuts down and
|
||||
@@ -177,6 +187,13 @@ for the full design + decision trail.
|
||||
4. **Crash recovery.** If Chromium crashes, the daemon exits immediately —
|
||||
no self-healing, don't hide failure. CLI detects the dead daemon on the
|
||||
next call and starts a fresh one.
|
||||
5. **Busy vs dead.** A daemon that stops answering HTTP while its process is
|
||||
alive is busy, not dead. The CLI gives `/health` a bounded ~8s to recover,
|
||||
then reports busy with a nonzero exit — it never kills an alive pid.
|
||||
Only an explicit `--force-restart` replaces a live-but-unresponsive
|
||||
daemon (tabs, cookies, and logins are lost). `browse stop` against a
|
||||
daemon that already died is success: the desired end state holds, so it
|
||||
cleans the stale state file instead of booting a daemon just to stop it.
|
||||
|
||||
### Multi-workspace isolation
|
||||
|
||||
@@ -186,8 +203,8 @@ collisions. State at `<project>/.gstack/browse.json`.
|
||||
|
||||
| Workspace | State file | Port |
|
||||
|-----------|-----------|------|
|
||||
| `/code/project-a` | `/code/project-a/.gstack/browse.json` | random (10000–60000) |
|
||||
| `/code/project-b` | `/code/project-b/.gstack/browse.json` | random (10000–60000) |
|
||||
| `/code/project-a` | `/code/project-a/.gstack/browse.json` | random (10000–49151) |
|
||||
| `/code/project-b` | `/code/project-b/.gstack/browse.json` | random (10000–49151) |
|
||||
|
||||
---
|
||||
|
||||
@@ -311,7 +328,7 @@ from `snapshot`, or `@c` refs from `snapshot -C`. Full table:
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| `status` | Daemon health + mode (headless / headed / cdp) |
|
||||
| `stop` | Shut down daemon |
|
||||
| `stop` | Shut down daemon (succeeds even if the daemon already died — never boots one just to stop it) |
|
||||
| `restart` | Restart daemon |
|
||||
| `connect` | Launch headed GStack Browser with Side Panel extension |
|
||||
| `disconnect` | Close headed Chrome, return to headless |
|
||||
@@ -319,6 +336,12 @@ from `snapshot`, or `@c` refs from `snapshot -C`. Full table:
|
||||
| `state save\|load <name>` | Save or load browser state (cookies + URLs) |
|
||||
| `memory [--json]` | Snapshot Bun heap + per-tab JS heap + Chromium process tree + bounded buffer sizes. Use `--json` for programmatic consumers; text mode renders sorted top-10 tabs with "and N more" tail. |
|
||||
|
||||
The daemon's own stdout/stderr persists to `<project>/.gstack/browse-daemon.log`
|
||||
(append mode, rotated to `.log.1` at the size cap, single generation), with
|
||||
tokens and unsanitized page content kept out — check it when a daemon dies
|
||||
without an obvious cause. A live-but-unresponsive daemon is never auto-killed;
|
||||
pass `--force-restart` to replace it explicitly (see "Daemon lifecycle" above).
|
||||
|
||||
### Handoff
|
||||
|
||||
| Command | Description |
|
||||
@@ -1210,8 +1233,8 @@ collisions.
|
||||
|
||||
| Workspace | State file | Port |
|
||||
|-----------|-----------|------|
|
||||
| `/code/project-a` | `/code/project-a/.gstack/browse.json` | random (10000–60000) |
|
||||
| `/code/project-b` | `/code/project-b/.gstack/browse.json` | random (10000–60000) |
|
||||
| `/code/project-a` | `/code/project-a/.gstack/browse.json` | random (10000–49151) |
|
||||
| `/code/project-b` | `/code/project-b/.gstack/browse.json` | random (10000–49151) |
|
||||
|
||||
Browser-skills three-tier lookup walks project → global → bundled, so a
|
||||
project-tier skill at `/code/project-a/.gstack/browser-skills/foo/` shadows
|
||||
@@ -1223,7 +1246,7 @@ the global `~/.gstack/browser-skills/foo/` only inside project-a.
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `BROWSE_PORT` | 0 (random 10000–60000) | Fixed port for the HTTP server (debug override) |
|
||||
| `BROWSE_PORT` | 0 (random 10000–49151) | Fixed port for the HTTP server (debug override) |
|
||||
| `BROWSE_IDLE_TIMEOUT` | 1800000 (30 min) | Idle shutdown timeout in ms |
|
||||
| `BROWSE_STATE_FILE` | `.gstack/browse.json` | Path to state file |
|
||||
| `BROWSE_SERVER_SCRIPT` | auto-detected | Path to `server.ts` |
|
||||
@@ -1250,6 +1273,8 @@ browse/
|
||||
│ ├── cli.ts # Thin client — reads state, sends HTTP, prints
|
||||
│ ├── server.ts # Bun HTTP daemon — routes commands, dual-listener
|
||||
│ ├── browser-manager.ts # Chromium lifecycle, tabs, ref map, crash detection
|
||||
│ ├── port-allocator.ts # Fixed 10000-49151 scan range for every long-lived listener (never port:0)
|
||||
│ ├── xprotect-heal.ts # macOS XProtect launch-kill classify + quarantine-clear + bounded reinstall
|
||||
│ ├── socks-bridge.ts # Local 127.0.0.1 SOCKS5 bridge that handles auth handshakes Chromium can't speak
|
||||
│ ├── proxy-config.ts # --proxy URL parsing + cred resolution (URL vs env, fail-fast on both)
|
||||
│ ├── proxy-redact.ts # Cred-redaction helper for any proxy URL surfaced to logs/errors
|
||||
@@ -1282,6 +1307,8 @@ browse/
|
||||
│ ├── content-security.ts # L1-L3: datamarking, hidden strip, ARIA, URL blocklist, envelopes
|
||||
│ ├── security.ts # L5 canary + L6 verdict combiner + thresholds
|
||||
│ ├── security-classifier.ts # L4 ML classifier (TestSavantAI, runs in the security sidecar)
|
||||
│ ├── security-sidecar-entry.ts # Sidecar subprocess entrypoint hosting the ONNX classifier
|
||||
│ ├── security-sidecar-client.ts # server.ts-side client that drives the sidecar
|
||||
│ ├── terminal-agent.ts # Side Panel Claude PTY manager (auth + lifecycle)
|
||||
│ ├── sidebar-utils.ts # Sidebar URL sanitization + helpers
|
||||
│ ├── cookie-import-browser.ts # Decrypt + import cookies from real Chromium browsers
|
||||
|
||||
+10
-9
@@ -6,12 +6,12 @@
|
||||
**memory sync never drops a record. 30 contributors landed.**
|
||||
|
||||
This release mines the full issue tracker and community PR queue. Browse now
|
||||
classifies a macOS XProtect kill at Chromium launch and heals itself — clears
|
||||
the quarantine flag, reinstalls the pinned browser revision from the right
|
||||
install root, and retries, all bounded and logged. Fresh installs link every
|
||||
runtime asset a skill references, so /review and friends work on a clean
|
||||
machine the first time. Brain-sync's queue is drained with a classified
|
||||
disposition — privacy-held records are retained and labeled, a failed push
|
||||
classifies a macOS XProtect kill at Chromium launch and heals itself. It
|
||||
clears the quarantine flag, reinstalls the pinned browser revision from the
|
||||
right install root, and retries, all bounded and logged. Fresh installs link
|
||||
every runtime asset a skill references, so /review and friends work on a
|
||||
clean machine the first time. Brain-sync's queue is drained with a classified
|
||||
disposition. Privacy-held records are retained and labeled, a failed push
|
||||
keeps its commit and re-delivers it on the next run, and the retry only ever
|
||||
publishes commits it authored itself. Twenty-five community PRs landed with
|
||||
credit, and roughly thirty-five issues close on merge.
|
||||
@@ -50,8 +50,9 @@ repro is now a regression test with your name on the commit.
|
||||
dependabot #2582), plus an XProtect kill-signature classifier with positive
|
||||
AND negative fixtures, a one-shot quarantine-clear + bounded (~120s,
|
||||
process-group-killed) reinstall from the gstack install root that pins the
|
||||
matching Chromium revision, structured heal logging, and a v1.65-style
|
||||
upgrade migration for already-poisoned caches. The heal resolves the install
|
||||
matching Chromium revision, structured heal logging, and an upgrade-time
|
||||
quarantine-clear + reinstall in `setup` for already-poisoned caches. The
|
||||
heal resolves the install
|
||||
root via `os.homedir()` and keeps its manual-remediation guidance even when
|
||||
the post-heal retry fails.
|
||||
- **Fresh installs missing runtime assets (#2317, #2454).** `setup` links
|
||||
@@ -90,7 +91,7 @@ repro is now a regression test with your name on the commit.
|
||||
(#2414).
|
||||
- Daemon crash logs persist without tokens or unsanitized page content
|
||||
(needle-tested). Contributed by @phuttimatebenchanakatkul (#2461).
|
||||
- The dead security-shield surface was removed end to end (−535 lines) while
|
||||
- The dead security-shield surface was removed end to end (−272 net lines) while
|
||||
the live L4 sidecar path keeps its status endpoint — docs updated in the
|
||||
same commit. Contributed by @frederik-kaster-noygear (#2557, with the
|
||||
pipe-capture core from #2559). CDP `Emulation.setEmulatedMedia` joins the
|
||||
|
||||
@@ -166,11 +166,12 @@ gstack/
|
||||
│ ├── test/ # Integration tests
|
||||
│ └── dist/ # Compiled binary
|
||||
├── 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, code-intelligence/)
|
||||
├── 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
|
||||
├── setup-deploy/ # /setup-deploy skill (one-time deploy config)
|
||||
├── .github/ # CI workflows + Docker image
|
||||
│ ├── workflows/ # evals.yml (E2E on Ubicloud), quality-gate.yml (secret scan), dependency-review.yml, osv-scanner.yml, skill-docs.yml, actionlint.yml, and 7 more (windows, periodic evals, release gates, ci-image)
|
||||
│ ├── 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
|
||||
@@ -455,8 +456,11 @@ symlink or a real copy. If it's a symlink to your working directory, be aware th
|
||||
global install at `~/.claude/skills/gstack/` is used instead
|
||||
|
||||
**Prefix setting:** Setup creates real directories (not symlinks) at the top level
|
||||
with a SKILL.md symlink inside (e.g., `qa/SKILL.md -> gstack/qa/SKILL.md`). This
|
||||
ensures Claude discovers them as top-level skills, not nested under `gstack/`.
|
||||
with a SKILL.md symlink inside (e.g., `qa/SKILL.md -> gstack/qa/SKILL.md`), plus
|
||||
links to each skill's runtime assets (sections/, templates, checklists — everything
|
||||
except SKILL.md, tests, build output, and `.tmpl` sources). Alias skills
|
||||
(`_gstack-command`, `connect-chrome`) install as rewritten copies, never symlinks.
|
||||
This ensures Claude discovers them as top-level skills, not nested under `gstack/`.
|
||||
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.
|
||||
@@ -660,6 +664,17 @@ claims v1.7.0.0 as a MINOR and branch B is also a MINOR, B lands at v1.8.0.0
|
||||
`bin/gstack-next-version` advances within the chosen bump level rather than
|
||||
repicking the level when collisions happen.
|
||||
|
||||
**package.json carries the npm-valid translation, not VERSION verbatim.**
|
||||
VERSION stays the 4-digit source of truth (e.g. `1.67.0.0`); package.json and
|
||||
any subdirectory manifests with a `version` field get the 3-digit npm-valid
|
||||
translation (`1.67.0`), and lockfile `version` fields sync only when the
|
||||
lockfile already exists. `bin/gstack-version-bump` (via `lib/version-source.ts`)
|
||||
owns the translation and judges drift on translated forms — do NOT "fix" the
|
||||
apparent mismatch by hand, and do not write a 4-digit version into
|
||||
package.json (npm rejects it). Rationale and translation rules live in the
|
||||
`lib/version-source.ts` header; `test/gstack-version-bump.test.ts` pins the
|
||||
contract.
|
||||
|
||||
**Scale-aware bumps — use common sense.** When the diff is big, bump MINOR (or
|
||||
MAJOR), not PATCH. PATCH is for bug fixes and small additions; MINOR is for
|
||||
substantial new capability or substantial reduction; MAJOR is for breaking
|
||||
|
||||
+13
-6
@@ -42,8 +42,10 @@ No setup needed. Learnings are logged automatically. View them with `/learn`.
|
||||
ln -sfn /path/to/your/gstack-fork .claude/skills/gstack
|
||||
cd .claude/skills/gstack && bun install && bun run build && ./setup
|
||||
```
|
||||
Setup creates per-skill directories with SKILL.md symlinks inside (`qa/SKILL.md -> gstack/qa/SKILL.md`)
|
||||
and asks your prefix preference. Pass `--no-prefix` to skip the prompt and use short names.
|
||||
Setup creates per-skill directories with SKILL.md symlinks inside (`qa/SKILL.md -> gstack/qa/SKILL.md`),
|
||||
links each skill's runtime assets alongside (sections/, templates, checklists — everything except
|
||||
SKILL.md, tests, build output, and `.tmpl` sources), and asks your prefix preference.
|
||||
Pass `--no-prefix` to skip the prompt and use short names.
|
||||
5. **Fix the issue** — your changes are live immediately in this project
|
||||
6. **Test by actually using gstack** — do the thing that annoyed you, verify it's fixed
|
||||
7. **Open a PR from your fork**
|
||||
@@ -82,7 +84,10 @@ gstack/ <- your working tree
|
||||
```
|
||||
|
||||
Setup creates real directories (not symlinks) at the top level with a SKILL.md
|
||||
symlink inside. This ensures Claude discovers them as top-level skills, not nested
|
||||
symlink inside, plus links to each skill's runtime assets (sections/, templates,
|
||||
checklists). Alias skills (`_gstack-command`, `connect-chrome`) install as
|
||||
rewritten copies, never symlinks — editing a symlinked alias would corrupt the
|
||||
generated source. This ensures Claude discovers them as top-level skills, not nested
|
||||
under `gstack/`. Names depend on your prefix setting (`~/.gstack/config.yaml`).
|
||||
Short names (`/review`, `/ship`) are the default. Run `./setup --prefix` if you
|
||||
prefer namespaced names (`/gstack-review`, `/gstack-ship`).
|
||||
@@ -118,9 +123,11 @@ passes `GSTACK_SKIP_GBRAIN_REGEN=1` inline to the nested `./setup` (so it never
|
||||
dirties tracked source) and runs `gen:skill-docs:user --out-dir .claude/gstack-rendered`,
|
||||
which rewrites only the section-base paths to point at the render. `bin/dev-teardown`
|
||||
removes the render. To make the blocks live across your *other* projects' Claude
|
||||
sessions, run `gstack-config gbrain-refresh`, which renders them into the global
|
||||
install (`~/.claude/skills/gstack`), guarded so it never touches a symlinked or
|
||||
non-gstack directory.
|
||||
sessions, run `gstack-config gbrain-refresh`, which renders them to a user render
|
||||
dir (`${GSTACK_USER_RENDER_DIR:-~/.gstack/render/claude}`, swapped in only on a
|
||||
successful render) and repoints the installed skills at it via `gstack-relink` —
|
||||
the global install checkout stays git-clean, and the refresh is guarded so it
|
||||
never touches a symlinked or non-gstack directory.
|
||||
|
||||
## Testing & evals
|
||||
|
||||
|
||||
@@ -254,6 +254,13 @@ Beyond the slash-command skills, gstack ships standalone CLIs for workflows that
|
||||
| `gstack-ios-qa-mint` | **iOS allowlist manager** — owner-grant CLI for the tailnet allowlist. `grant`/`revoke`/`list` against `~/.gstack/ios-qa-allowlist.json` (mode 0600). Remote agents never auto-allowlist; this is the explicit-intent path. |
|
||||
| `gstack-ios-qa-regen` | **iOS bridge regenerator** — deterministically installs the canonical DebugBridge package, generates typed state accessors, and records the installed gstack version. Safe to rerun after source changes or upgrades. |
|
||||
|
||||
`./setup` also registers one default-on Stop hook in `~/.claude/settings.json`:
|
||||
`gstack-timeline-stop` (closes dangling session-timeline entries when a session
|
||||
is interrupted; fail-open — 2s internal budget, always exits 0, can never block
|
||||
a session). Skip it with `./setup --no-team`, remove it with
|
||||
`gstack-settings-hook remove-source --source gstack-timeline-stop`;
|
||||
`gstack-uninstall` removes it too.
|
||||
|
||||
### Continuous checkpoint mode (opt-in, local by default)
|
||||
|
||||
Set `gstack-config set checkpoint_mode continuous` and skills auto-commit your work as you go with a `WIP:` prefix plus a structured `[gstack-context]` body (decisions, remaining work, failed approaches). Survives crashes and context switches. `/context-restore` reads those commits to reconstruct session state. `/ship` filter-squashes WIP commits before the PR (preserving non-WIP commits) so bisect stays clean. Push is opt-in via `checkpoint_push=true` — default is local-only so you don't trigger CI on every WIP commit.
|
||||
@@ -297,7 +304,7 @@ gstack works well with one sprint. It gets interesting with ten running at once.
|
||||
|
||||
**Personal automation.** The sidebar agent isn't just for dev workflows. Example: "Browse my kid's school parent portal and add all the other parents' names, phone numbers, and photos to my Google Contacts." Two ways to get authenticated: (1) log in once in the headed browser, your session persists, or (2) click the "cookies" button in the sidebar footer to import cookies from your real Chrome. Once authenticated, Claude navigates the directory, extracts the data, and creates the contacts.
|
||||
|
||||
**Prompt injection defense.** Hostile web pages try to hijack your sidebar agent. gstack ships a layered defense: a 22MB ML classifier bundled with the browser scans every page and tool output locally, a Claude Haiku transcript check votes on the full conversation shape, a random canary token in the system prompt catches session exfil attempts across text, tool args, URLs, and file writes, and a verdict combiner requires two classifiers to agree before blocking (prevents single-model false positives on Stack Overflow-style instruction pages). A shield icon in the sidebar header shows status (green/amber/red). Opt in to a 721MB DeBERTa-v3 ensemble via `GSTACK_SECURITY_ENSEMBLE=deberta` for 2-of-3 agreement. Emergency kill switch: `GSTACK_SECURITY_OFF=1`. See [ARCHITECTURE.md](ARCHITECTURE.md#prompt-injection-defense-sidebar-agent) for the full stack.
|
||||
**Prompt injection defense.** Hostile web pages try to hijack your sidebar agent. gstack ships a layered defense: content filters (datamarking, hidden-element stripping, ARIA scrubbing, URL blocklist) on every page read, plus a 22MB ML classifier running locally in a sidecar subprocess that scans page-derived content before the agent sees it, with a verdict combiner that requires classifier agreement before blocking (prevents single-model false positives on Stack Overflow-style instruction pages). Everything runs on your machine, no network calls. Emergency kill switch: `GSTACK_SECURITY_OFF=1`. See [ARCHITECTURE.md](ARCHITECTURE.md#prompt-injection-defense-sidebar-agent) for the full stack.
|
||||
|
||||
**Browser handoff when the AI gets stuck.** Hit a CAPTCHA, auth wall, or MFA prompt? `$B handoff` opens a visible Chrome at the exact same page with all your cookies and tabs intact. Solve the problem, tell Claude you're done, `$B resume` picks up right where it left off. The agent even suggests it automatically after 3 consecutive failures.
|
||||
|
||||
@@ -344,6 +351,7 @@ If you don't have the repo cloned (e.g. you installed via a Claude Code paste an
|
||||
pkill -f "gstack.*browse" 2>/dev/null || true
|
||||
|
||||
# 2. Remove per-skill directories whose SKILL.md points into gstack/
|
||||
# (rm -rf, not rmdir — installed dirs also contain runtime-asset links)
|
||||
find ~/.claude/skills -mindepth 1 -maxdepth 1 -type d ! -name gstack 2>/dev/null |
|
||||
while IFS= read -r dir; do
|
||||
link="$dir/SKILL.md"
|
||||
@@ -351,11 +359,12 @@ while IFS= read -r dir; do
|
||||
target=$(readlink "$link" 2>/dev/null) || continue
|
||||
case "$target" in
|
||||
gstack/*|*/gstack/*)
|
||||
rm -f "$link"
|
||||
rmdir "$dir" 2>/dev/null || true
|
||||
rm -rf "$dir"
|
||||
;;
|
||||
esac
|
||||
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
|
||||
|
||||
# 3. Remove gstack
|
||||
rm -rf ~/.claude/skills/gstack
|
||||
@@ -368,6 +377,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 ~/.cursor/skills/gstack* 2>/dev/null
|
||||
rm -rf ~/.config/opencode/skills/gstack* 2>/dev/null
|
||||
|
||||
# 6. Remove temp files
|
||||
rm -f /tmp/gstack-* 2>/dev/null
|
||||
@@ -377,6 +388,10 @@ rm -rf .gstack .gstack-worktrees .claude/skills/gstack 2>/dev/null
|
||||
rm -rf .agents/skills/gstack* .factory/skills/gstack* 2>/dev/null
|
||||
```
|
||||
|
||||
Manual removal leaves the gstack Stop hook entry behind in `~/.claude/settings.json`
|
||||
(the uninstall script removes it for you). Edit that file and delete the hook whose
|
||||
command path ends in `hosts/claude/hooks/timeline-stop-hook`.
|
||||
|
||||
### Clean up CLAUDE.md
|
||||
|
||||
The uninstall script does not edit CLAUDE.md. In each project where gstack was added, remove the `## gstack` and `## Skill routing` sections.
|
||||
|
||||
Reference in New Issue
Block a user