mirror of
https://github.com/garrytan/gstack.git
synced 2026-05-21 20:28:24 +08:00
fix: resolve merge conflicts with origin/main (v0.4.2 base branch detection)
Merge origin/main which added: - BASE_BRANCH_DETECT placeholder + dynamic branch detection in all skills - Updated contributor mode (reflection-based, 0-10 rating) - Async await wrapping in browse js/eval commands - Hardcoded-main regression test Resolved conflicts: - VERSION: keep 0.6.0 (our version, above 0.4.2) - CHANGELOG: both entries preserved (0.6.0 above 0.4.2) - gen-skill-docs.ts: keep main's updated contributor mode, add our escalation protocol - review/SKILL.md.tmpl: fix hardcoded 'origin/main' in Step 1.5 to use origin/<base> Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -44,12 +44,15 @@ Per-skill instructions may add additional formatting rules on top of this baseli
|
||||
|
||||
## Contributor Mode
|
||||
|
||||
If `_CONTRIB` is `true`: you are in **contributor mode**. When you hit friction with **gstack itself** (not the user's app), file a field report. Think: "hey, I was trying to do X with gstack and it didn't work / was confusing / was annoying. Here's what happened."
|
||||
If `_CONTRIB` is `true`: you are in **contributor mode**. You're a gstack user who also helps make it better.
|
||||
|
||||
**gstack issues:** browse command fails/wrong output, snapshot missing elements, skill instructions unclear or misleading, binary crash/hang, unhelpful error message, any rough edge or annoyance — even minor stuff.
|
||||
**NOT gstack issues:** user's app bugs, network errors to user's URL, auth failures on user's site.
|
||||
**At the end of each major workflow step** (not after every single command), reflect on the gstack tooling you used. Rate your experience 0 to 10. If it wasn't a 10, think about why. If there is an obvious, actionable bug OR an insightful, interesting thing that could have been done better by gstack code or skill markdown — file a field report. Maybe our contributor will help make us better!
|
||||
|
||||
**To file:** write `~/.gstack/contributor-logs/{slug}.md` with this structure:
|
||||
**Calibration — this is the bar:** For example, `$B js "await fetch(...)"` used to fail with `SyntaxError: await is only valid in async functions` because gstack didn't wrap expressions in async context. Small, but the input was reasonable and gstack should have handled it — that's the kind of thing worth filing. Things less consequential than this, ignore.
|
||||
|
||||
**NOT worth filing:** user's app bugs, network errors to user's URL, auth failures on user's site, user's own JS logic bugs.
|
||||
|
||||
**To file:** write `~/.gstack/contributor-logs/{slug}.md` with **all sections below** (do not truncate — include every section through the Date/Version footer):
|
||||
|
||||
```
|
||||
# {Title}
|
||||
@@ -58,20 +61,23 @@ Hey gstack team — ran into this while using /{skill-name}:
|
||||
|
||||
**What I was trying to do:** {what the user/agent was attempting}
|
||||
**What happened instead:** {what actually happened}
|
||||
**How annoying (1-5):** {1=meh, 3=friction, 5=blocker}
|
||||
**My rating:** {0-10} — {one sentence on why it wasn't a 10}
|
||||
|
||||
## Steps to reproduce
|
||||
1. {step}
|
||||
|
||||
## Raw output
|
||||
(wrap any error messages or unexpected output in a markdown code block)
|
||||
```
|
||||
{paste the actual error or unexpected output here}
|
||||
```
|
||||
|
||||
## What would make this a 10
|
||||
{one sentence: what gstack should have done differently}
|
||||
|
||||
**Date:** {YYYY-MM-DD} | **Version:** {gstack version} | **Skill:** /{skill}
|
||||
```
|
||||
|
||||
Then run: `mkdir -p ~/.gstack/contributor-logs && open ~/.gstack/contributor-logs/{slug}.md`
|
||||
|
||||
Slug: lowercase, hyphens, max 60 chars (e.g. `browse-snapshot-ref-gap`). Skip if file already exists. Max 3 reports per session. File inline and continue — don't stop the workflow. Tell user: "Filed gstack field report: {title}"
|
||||
Slug: lowercase, hyphens, max 60 chars (e.g. `browse-js-no-await`). Skip if file already exists. Max 3 reports per session. File inline and continue — don't stop the workflow. Tell user: "Filed gstack field report: {title}"
|
||||
|
||||
## Completion Status Protocol
|
||||
|
||||
|
||||
@@ -11,6 +11,12 @@ import type { Page } from 'playwright';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
/** Detect await keyword, ignoring comments. Accepted risk: await in string literals triggers wrapping (harmless). */
|
||||
function hasAwait(code: string): boolean {
|
||||
const stripped = code.replace(/\/\/.*$/gm, '').replace(/\/\*[\s\S]*?\*\//g, '');
|
||||
return /\bawait\b/.test(stripped);
|
||||
}
|
||||
|
||||
// Security: Path validation to prevent path traversal attacks
|
||||
const SAFE_DIRECTORIES = ['/tmp', process.cwd()];
|
||||
|
||||
@@ -118,7 +124,8 @@ export async function handleReadCommand(
|
||||
case 'js': {
|
||||
const expr = args[0];
|
||||
if (!expr) throw new Error('Usage: browse js <expression>');
|
||||
const result = await page.evaluate(expr);
|
||||
const wrapped = hasAwait(expr) ? `(async()=>(${expr}))()` : expr;
|
||||
const result = await page.evaluate(wrapped);
|
||||
return typeof result === 'object' ? JSON.stringify(result, null, 2) : String(result ?? '');
|
||||
}
|
||||
|
||||
@@ -128,6 +135,13 @@ export async function handleReadCommand(
|
||||
validateReadPath(filePath);
|
||||
if (!fs.existsSync(filePath)) throw new Error(`File not found: ${filePath}`);
|
||||
const code = fs.readFileSync(filePath, 'utf-8');
|
||||
if (hasAwait(code)) {
|
||||
const trimmed = code.trim();
|
||||
const isSingleExpr = trimmed.split('\n').length === 1;
|
||||
const wrapped = isSingleExpr ? `(async()=>(${trimmed}))()` : `(async()=>{\n${code}\n})()`;
|
||||
const result = await page.evaluate(wrapped);
|
||||
return typeof result === 'object' ? JSON.stringify(result, null, 2) : String(result ?? '');
|
||||
}
|
||||
const result = await page.evaluate(code);
|
||||
return typeof result === 'object' ? JSON.stringify(result, null, 2) : String(result ?? '');
|
||||
}
|
||||
|
||||
@@ -144,6 +144,60 @@ describe('Inspection', () => {
|
||||
expect(obj.b).toBe(2);
|
||||
});
|
||||
|
||||
test('js supports await expressions', async () => {
|
||||
const result = await handleReadCommand('js', ['await Promise.resolve(42)'], bm);
|
||||
expect(result).toBe('42');
|
||||
});
|
||||
|
||||
test('js does not false-positive on await substring', async () => {
|
||||
const result = await handleReadCommand('js', ['(() => { const awaitable = 5; return awaitable })()'], bm);
|
||||
expect(result).toBe('5');
|
||||
});
|
||||
|
||||
test('eval supports await in single-line file', async () => {
|
||||
const tmp = '/tmp/eval-await-test.js';
|
||||
fs.writeFileSync(tmp, 'await Promise.resolve("hello from eval")');
|
||||
try {
|
||||
const result = await handleReadCommand('eval', [tmp], bm);
|
||||
expect(result).toBe('hello from eval');
|
||||
} finally {
|
||||
fs.unlinkSync(tmp);
|
||||
}
|
||||
});
|
||||
|
||||
test('eval does not wrap when await is only in a comment', async () => {
|
||||
const tmp = '/tmp/eval-comment-test.js';
|
||||
fs.writeFileSync(tmp, '// no need to await this\ndocument.title');
|
||||
try {
|
||||
const result = await handleReadCommand('eval', [tmp], bm);
|
||||
expect(result).toBe('Test Page - Basic');
|
||||
} finally {
|
||||
fs.unlinkSync(tmp);
|
||||
}
|
||||
});
|
||||
|
||||
test('eval multi-line with await and explicit return', async () => {
|
||||
const tmp = '/tmp/eval-multiline-await.js';
|
||||
fs.writeFileSync(tmp, 'const data = await Promise.resolve("multi");\nreturn data;');
|
||||
try {
|
||||
const result = await handleReadCommand('eval', [tmp], bm);
|
||||
expect(result).toBe('multi');
|
||||
} finally {
|
||||
fs.unlinkSync(tmp);
|
||||
}
|
||||
});
|
||||
|
||||
test('eval multi-line with await but no return gives empty string', async () => {
|
||||
const tmp = '/tmp/eval-multiline-no-return.js';
|
||||
fs.writeFileSync(tmp, 'const data = await Promise.resolve("lost");\ndata;');
|
||||
try {
|
||||
const result = await handleReadCommand('eval', [tmp], bm);
|
||||
expect(result).toBe('');
|
||||
} finally {
|
||||
fs.unlinkSync(tmp);
|
||||
}
|
||||
});
|
||||
|
||||
test('css returns computed property', async () => {
|
||||
const result = await handleReadCommand('css', ['h1', 'color'], bm);
|
||||
// Navy color
|
||||
|
||||
Reference in New Issue
Block a user