CodeVetter · Repo Unpack · Data flow
garrytan/gstack
There are four main data pipelines. (1) Doc generation: .tmpl + resolvers + host config → committed SKILL.md per host. (2) Browser commands: CLI → state file → localhost POST /command → scoped-token check → Playwright/CDP → sanitizers + untrusted-content envelope → stdout. (3) Rendering: a local HTML file is served on a loopback Bun.serve port and printed/screenshotted through Aside or the browse daemon. (4) Telemetry/egress: every off-machine send writes a hash-chained receipt before transmitting; eval results land under ~/.gstack/projects/<slug>/evals/. State ownership is split between per-project .gstack/ (daemon state, logs) and the machine-level state root from bin/gstack-paths (sessions, egress ledger, security logs, evals).
- Skill generation pipeline: SKILL.md.tmpl → scripts/gen-skill-docs.ts finds {{PLACEHOLDER}} tokens → RESOLVERS functions produce text → per-host frontmatter transform (allowlist/denylist, descriptionLimit, conditionalFields), pathRewrites and toolRewrites → writes SKILL.md (claude) or <hostSubdir>/skills output (other hosts); --dry-run powers CI freshness checks.
- gen-skill-docs also emits two non-skill generated artifacts: review/design-checklist.md (from lib/design-catalog.ts via scripts/resolvers/design-checklist.ts) and lib/dom-dump.js (from lib/dom-dump-script.ts); test/design-checklist-sync.test.ts fails on drift.
- A browse command flows: cli.ts readState() → isServerHealthy() GET /health → spawn server.ts if absent → POST /command with Authorization: Bearer <token> → server validates token (root or scoped via token-registry checkScope/checkDomain/checkRate) → handleReadCommand/handleWriteCommand/handleMetaCommand → Playwright → result.
- Page-derived output is wrapped as untrusted: PAGE_CONTENT_COMMANDS results pass through wrapUntrustedContent (BEGIN/END UNTRUSTED EXTERNAL CONTENT markers with marker-escape and URL sanitization), and DOM_CONTENT_COMMANDS additionally run markHiddenElements content filters in browse/src/content-security.ts before the read.
- Every server egress of page-content strings routes through Unicode lone-surrogate sanitization: handleCommandInternal wraps the result string, and the SSE producers (/activity/stream, /inspector/events) stringify with sanitizeReplacer — an invariant pinned by browse/test/server-sanitize-surrogates.test.ts.
- Browser event data (console, network, dialog) accumulates in three 50K-entry in-memory CircularBuffers flushed asynchronously to .gstack/*.log every ~1s; the console/network/dialog commands read memory, disk is for post-mortem.
- The render pipeline: bin/gstack-render.ts parses argv into a RenderSpec → lib/aside-render.ts pickEngine() → probeAside() (aside --version + aside repl ASIDE_READY probe) → serve the file's directory on 127.0.0.1 with a per-render secret path segment → one aside repl script emits GSTACK_RENDER_OK + ASIDE_DIR → artifacts copied out; on the fallback path the same spec is executed as newtab/goto/pdf --from-file/screenshot/closetab daemon calls.
- Off-machine sends are receipted before transmission: lib/egress-receipt.ts writeReceipt appends a content-free (sha256+bytes, never payload), hash-chained line to ~/.gstack/security/egress.jsonl (0600); shell sinks use _receipted_curl/_receipted_git from bin/gstack-egress-lib.sh; failure polarity is fail-closed for sensitive sinks and fail-open for user-facing ones.
- Every skill invocation begins by running bin/gstack-skill-start (the consolidated preamble runtime that replaced ~18KB of inline bash): it performs update check, session tracking under ~/.gstack/sessions/, contributor mode, and emits KEY: value STATUS lines the skill prose branches on; bin/gstack-skill-end logs telemetry at completion.
- Paid-eval selection is data-driven: test/helpers/touchfiles.ts re-exports literal-only maps (E2E_TOUCHFILES, E2E_TIERS, LLM_JUDGE_TOUCHFILES, GLOBAL_TOUCHFILES) from touchfiles-data.ts; test-selection.ts computes selectTests() from `git diff` against the detected base branch; results persist via test/helpers/eval-store.ts to ~/.gstack/projects/<slug>/evals/.
- Multi-agent browser access uses scoped tokens: root token → POST /token or POST /connect setup-key exchange → scoped tokens carrying subsets of read/write/admin/meta/control scopes derived-but-reclassified from commands.ts (js/eval/cookies land in admin, not read); control is never granted by default.
- Cookie import reads the real browser's Chromium SQLite cookie DB via a copied temp file opened read-only, decrypts values in-process (PBKDF2 + AES-128-CBC after a user-approved macOS Keychain prompt), loads them into the Playwright context, and never writes plaintext to disk or logs.
- Model selection is orthogonal to host: scripts/models.ts ALL_MODEL_NAMES + resolveModel() normalize CLI input to a family; the generator renders model-overlays/<family>.md per host using HostConfig.defaultModel unless --model is explicit; setup reads Codex's config.toml via scripts/resolve-codex-generation-model.ts.
- The Aside research path funnels through a generated `_aside_exec` bash wrapper ({{ASIDE_EXEC_PRELUDE}}) that writes an egress receipt before the prompt leaves the machine — skills are forbidden from calling `aside exec` bare.