mirror of
https://github.com/garrytan/gstack.git
synced 2026-09-09 22:48:57 +02:00
fix: pre-landing review fixes for the Aside-first branch
Review army + adversarial passes (Claude and Codex) on the merged branch:
setup
- _prune_stale_generated scans the host dirs too (the generator already
removed the render before setup ran, so the host branch was dead), skips
symlinks in the render tree (rm -rf on a slash-terminated link empties its
target), removes a host symlink only when it resolves into gstack, cleans a
bannered real dir through _cleanup_weak_dir, recognizes frontmatter-renamed
skills, and logs through log. The always-run codex render passes every host
dir that may link to it.
- NEEDS_BUILD checks all three binaries (with $_EXE) and lib/ sources; the
browser hint and the bootstrap summary honor GSTACK_SKIP_ASIDE, treat a
requested skip as a request, and derive one skill list.
lib/aside-render.ts + bin/gstack-render.ts
- The loopback server carries a per-render secret path, checks containment on
the real path (symlink escapes are 403), and rejects malformed encoding.
- Inline eval results are one base64 line, so page text cannot forge
ASIDE_DIR= or the sentinel; the last ASIDE_DIR wins.
- runProc escalates SIGTERM to SIGKILL, bounds every wait, and clears every
timer (an uncleared one kept gstack-render alive after printing OK).
- renderTmpDir refuses a shared /tmp name owned by someone else; the work dir
and server are created inside try; goto's budget follows the render budget.
- probeAside classifies a present-but-failing CLI as ASIDE_NOT_RUNNING like
the skills' bash probe; render() retries on gstack's own browser when Aside
could not start or its private CDP bridge is gone (never on a page error
or a timeout of a running script); the CLI reports the engine that actually
rendered, exits 0 on --help, rejects non-numeric flags, documents
--wait-timeout, fences EVAL/PAGE_ERRORS as untrusted content, and names the
daemon's cookie-import JS lock remedy.
- The browse path passes --scale only when asked (a scale change rebuilds
the daemon context) and restores the viewport after a sized screenshot.
resolvers / templates
- The bash probe honors GSTACK_SKIP_ASIDE and has a perl deadline on stock
macOS; .local is no longer LOCAL (mDNS); same-origin filters compare parsed
origins; link status is HEAD-checked only on LOCAL targets; every
aside exec goes through the receipted _aside_exec prelude
({{ASIDE_EXEC_PRELUDE}}), including nine template blocks that called it
bare; the design sketch and diagram staging use private directories.
- The generator prunes only bannered renders and never a host whose
generation failed.
Docs, stale comments and dead code cleaned; goldens re-rendered; tests
updated and added for every behavior above.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
ea61bd65be
commit
444f8feff8
@@ -1177,15 +1177,25 @@ if (!DRY_RUN) {
|
||||
// Prune stale external-host outputs. A run always renders every skill for the
|
||||
// chosen host(s) (there is no per-skill filter), so any `gstack-*` directory
|
||||
// left in <host>/skills/ that this run did not write belongs to a skill that
|
||||
// no longer exists. Symlinks (the `gstack` sidecar) and non-prefixed entries
|
||||
// are never touched.
|
||||
// no longer exists. Symlinks (the `gstack` sidecar), non-prefixed entries, and
|
||||
// gstack-* directories without the generated banner (someone's own skill) are
|
||||
// never touched.
|
||||
if (!DRY_RUN) {
|
||||
// A host whose generation threw has a PARTIAL rendered set: pruning against
|
||||
// it would delete every valid render the loop never reached. Skip those.
|
||||
const failedHosts = new Set(failures.map((f) => f.host));
|
||||
for (const [host, names] of RENDERED_EXTERNAL) {
|
||||
if (failedHosts.has(host)) { console.error(` prune skipped for ${host}: generation failed, rendered set is partial`); continue; }
|
||||
const skillsRoot = path.join(OUT_DIR ?? ROOT, getHostConfig(host as Host).hostSubdir, 'skills');
|
||||
let entries: fs.Dirent[] = [];
|
||||
try { entries = fs.readdirSync(skillsRoot, { withFileTypes: true }); } catch { continue; }
|
||||
for (const e of entries) {
|
||||
if (e.isSymbolicLink() || !e.isDirectory() || !e.name.startsWith('gstack-') || names.has(e.name)) continue;
|
||||
// Only a directory we provably rendered (the generated banner in its
|
||||
// SKILL.md) may be deleted whole — a hand-authored gstack-* dir is kept.
|
||||
let generated = false;
|
||||
try { generated = fs.readFileSync(path.join(skillsRoot, e.name, 'SKILL.md'), 'utf-8').includes('<!-- AUTO-GENERATED from'); } catch { generated = false; }
|
||||
if (!generated) { console.log(` kept ${host} skills/${e.name}: not a gstack render (no generated banner)`); continue; }
|
||||
fs.rmSync(path.join(skillsRoot, e.name), { recursive: true, force: true });
|
||||
console.log(` pruned stale ${host} render: ${e.name}`);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user