mirror of
https://github.com/garrytan/gstack.git
synced 2026-05-07 05:56:41 +02:00
5205070299
* refactor: extract command registry to commands.ts, add SNAPSHOT_FLAGS metadata
- NEW: browse/src/commands.ts — command sets + COMMAND_DESCRIPTIONS + load-time validation (zero side effects)
- server.ts imports from commands.ts instead of declaring sets inline
- snapshot.ts: SNAPSHOT_FLAGS array drives parseSnapshotArgs (metadata-driven, no duplication)
- All 186 existing tests pass
* feat: SKILL.md template system with auto-generated command references
- SKILL.md.tmpl + browse/SKILL.md.tmpl with {{COMMAND_REFERENCE}} and {{SNAPSHOT_FLAGS}} placeholders
- scripts/gen-skill-docs.ts generates SKILL.md from templates (supports --dry-run)
- Build pipeline runs gen:skill-docs before binary compilation
- Generated files have AUTO-GENERATED header, committed to git
* test: Tier 1 static validation — 34 tests for SKILL.md command correctness
- test/helpers/skill-parser.ts: extracts $B commands from code blocks, validates against registry
- test/skill-parser.test.ts: 13 parser/validator unit tests
- test/skill-validation.test.ts: 13 tests validating all SKILL.md files + registry consistency
- test/gen-skill-docs.test.ts: 8 generator tests (categories, sorting, freshness)
* feat: DX tools (skill:check, dev:skill) + Tier 2 E2E test scaffolding
- scripts/skill-check.ts: health summary for all SKILL.md files (commands, templates, freshness)
- scripts/dev-skill.ts: watch mode for template development
- test/helpers/session-runner.ts: Agent SDK wrapper for E2E skill tests
- test/skill-e2e.test.ts: 2 E2E tests + 3 stubs (auto-skip inside Claude Code sessions)
- E2E tests must run from plain terminal: SKILL_E2E=1 bun test test/skill-e2e.test.ts
* ci: SKILL.md freshness check on push/PR + TODO updates
- .github/workflows/skill-docs.yml: fails if generated SKILL.md files are stale
- TODO.md: add E2E cost tracking and model pinning to future ideas
* fix: restore rich descriptions lost in auto-generation
- Snapshot flags: add back value hints (-d <N>, -s <sel>, -o <path>)
- Snapshot flags: restore parenthetical context (@e refs, @c refs, etc.)
- Commands: is → includes valid states enum
- Commands: console → notes --errors filter behavior
- Commands: press → lists common keys (Enter, Tab, Escape)
- Commands: cookie-import-browser → describes picker UI
- Commands: dialog-accept → specifies alert/confirm/prompt
- Tips: restore → arrow (was downgraded to ->)
* test: quality evals for generated SKILL.md descriptions
Catches the exact regressions we shipped and caught in review:
- Snapshot flags must include value hints (-d <N>, -s <sel>, -o <path>)
- is command must list all valid states (visible/hidden/enabled/...)
- press command must list example keys (Enter, Tab, Escape)
- console command must describe --errors behavior
- Snapshot -i must mention @e refs, -C must mention @c refs
- All descriptions must be >= 8 chars (no empty stubs)
- Tips section must use → not ->
* feat: LLM-as-judge evals for SKILL.md documentation quality
4 eval tests using Anthropic API (claude-haiku, ~$0.01-0.03/run):
- Command reference table: clarity/completeness/actionability >= 4/5
- Snapshot flags section: same thresholds
- browse/SKILL.md overall quality
- Regression: generated version must score >= hand-maintained baseline
Requires ANTHROPIC_API_KEY. Auto-skips without it.
Run: bun run test:eval (or ANTHROPIC_API_KEY=sk-... bun test test/skill-llm-eval.test.ts)
* chore: bump version to 0.3.3, update changelog
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: add ARCHITECTURE.md, update CLAUDE.md and CONTRIBUTING.md
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: conductor.json lifecycle hooks + .env propagation across worktrees
bin/dev-setup now copies .env from main worktree so API keys carry
over to Conductor workspaces automatically. conductor.json wires up
setup and archive hooks.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: complete CHANGELOG for v0.3.3 (architecture, conductor, .env)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
108 lines
6.7 KiB
TypeScript
108 lines
6.7 KiB
TypeScript
/**
|
|
* Command registry — single source of truth for all browse commands.
|
|
*
|
|
* Dependency graph:
|
|
* commands.ts ──▶ server.ts (runtime dispatch)
|
|
* ──▶ gen-skill-docs.ts (doc generation)
|
|
* ──▶ skill-parser.ts (validation)
|
|
* ──▶ skill-check.ts (health reporting)
|
|
*
|
|
* Zero side effects. Safe to import from build scripts and tests.
|
|
*/
|
|
|
|
export const READ_COMMANDS = new Set([
|
|
'text', 'html', 'links', 'forms', 'accessibility',
|
|
'js', 'eval', 'css', 'attrs',
|
|
'console', 'network', 'cookies', 'storage', 'perf',
|
|
'dialog', 'is',
|
|
]);
|
|
|
|
export const WRITE_COMMANDS = new Set([
|
|
'goto', 'back', 'forward', 'reload',
|
|
'click', 'fill', 'select', 'hover', 'type', 'press', 'scroll', 'wait',
|
|
'viewport', 'cookie', 'cookie-import', 'cookie-import-browser', 'header', 'useragent',
|
|
'upload', 'dialog-accept', 'dialog-dismiss',
|
|
]);
|
|
|
|
export const META_COMMANDS = new Set([
|
|
'tabs', 'tab', 'newtab', 'closetab',
|
|
'status', 'stop', 'restart',
|
|
'screenshot', 'pdf', 'responsive',
|
|
'chain', 'diff',
|
|
'url', 'snapshot',
|
|
]);
|
|
|
|
export const ALL_COMMANDS = new Set([...READ_COMMANDS, ...WRITE_COMMANDS, ...META_COMMANDS]);
|
|
|
|
export const COMMAND_DESCRIPTIONS: Record<string, { category: string; description: string; usage?: string }> = {
|
|
// Navigation
|
|
'goto': { category: 'Navigation', description: 'Navigate to URL', usage: 'goto <url>' },
|
|
'back': { category: 'Navigation', description: 'History back' },
|
|
'forward': { category: 'Navigation', description: 'History forward' },
|
|
'reload': { category: 'Navigation', description: 'Reload page' },
|
|
'url': { category: 'Navigation', description: 'Print current URL' },
|
|
// Reading
|
|
'text': { category: 'Reading', description: 'Cleaned page text' },
|
|
'html': { category: 'Reading', description: 'innerHTML', usage: 'html [selector]' },
|
|
'links': { category: 'Reading', description: 'All links as "text → href"' },
|
|
'forms': { category: 'Reading', description: 'Form fields as JSON' },
|
|
'accessibility': { category: 'Reading', description: 'Full ARIA tree' },
|
|
// Inspection
|
|
'js': { category: 'Inspection', description: 'Run JavaScript', usage: 'js <expr>' },
|
|
'eval': { category: 'Inspection', description: 'Run JS file', usage: 'eval <file>' },
|
|
'css': { category: 'Inspection', description: 'Computed CSS value', usage: 'css <sel> <prop>' },
|
|
'attrs': { category: 'Inspection', description: 'Element attributes as JSON', usage: 'attrs <sel|@ref>' },
|
|
'is': { category: 'Inspection', description: 'State check (visible/hidden/enabled/disabled/checked/editable/focused)', usage: 'is <prop> <sel>' },
|
|
'console': { category: 'Inspection', description: 'Console messages (--errors filters to error/warning)', usage: 'console [--clear|--errors]' },
|
|
'network': { category: 'Inspection', description: 'Network requests', usage: 'network [--clear]' },
|
|
'dialog': { category: 'Inspection', description: 'Dialog messages', usage: 'dialog [--clear]' },
|
|
'cookies': { category: 'Inspection', description: 'All cookies as JSON' },
|
|
'storage': { category: 'Inspection', description: 'localStorage + sessionStorage', usage: 'storage [set k v]' },
|
|
'perf': { category: 'Inspection', description: 'Page load timings' },
|
|
// Interaction
|
|
'click': { category: 'Interaction', description: 'Click element', usage: 'click <sel>' },
|
|
'fill': { category: 'Interaction', description: 'Fill input', usage: 'fill <sel> <val>' },
|
|
'select': { category: 'Interaction', description: 'Select dropdown option', usage: 'select <sel> <val>' },
|
|
'hover': { category: 'Interaction', description: 'Hover element', usage: 'hover <sel>' },
|
|
'type': { category: 'Interaction', description: 'Type into focused element', usage: 'type <text>' },
|
|
'press': { category: 'Interaction', description: 'Press key (Enter, Tab, Escape, etc.)', usage: 'press <key>' },
|
|
'scroll': { category: 'Interaction', description: 'Scroll element into view', usage: 'scroll [sel]' },
|
|
'wait': { category: 'Interaction', description: 'Wait for element/condition', usage: 'wait <sel|--networkidle|--load>' },
|
|
'upload': { category: 'Interaction', description: 'Upload file(s)', usage: 'upload <sel> <file...>' },
|
|
'viewport':{ category: 'Interaction', description: 'Set viewport size', usage: 'viewport <WxH>' },
|
|
'cookie': { category: 'Interaction', description: 'Set cookie' },
|
|
'cookie-import': { category: 'Interaction', description: 'Import cookies from JSON file', usage: 'cookie-import <json>' },
|
|
'cookie-import-browser': { category: 'Interaction', description: 'Import cookies from real browser (opens picker UI, or direct with --domain)', usage: 'cookie-import-browser [browser] [--domain d]' },
|
|
'header': { category: 'Interaction', description: 'Set custom request header', usage: 'header <name> <value>' },
|
|
'useragent': { category: 'Interaction', description: 'Set user agent', usage: 'useragent <string>' },
|
|
'dialog-accept': { category: 'Interaction', description: 'Auto-accept next alert/confirm/prompt', usage: 'dialog-accept [text]' },
|
|
'dialog-dismiss': { category: 'Interaction', description: 'Auto-dismiss next dialog' },
|
|
// Visual
|
|
'screenshot': { category: 'Visual', description: 'Save screenshot', usage: 'screenshot [path]' },
|
|
'pdf': { category: 'Visual', description: 'Save as PDF', usage: 'pdf [path]' },
|
|
'responsive': { category: 'Visual', description: 'Mobile/tablet/desktop screenshots', usage: 'responsive [prefix]' },
|
|
'diff': { category: 'Visual', description: 'Text diff between pages', usage: 'diff <url1> <url2>' },
|
|
// Tabs
|
|
'tabs': { category: 'Tabs', description: 'List open tabs' },
|
|
'tab': { category: 'Tabs', description: 'Switch to tab', usage: 'tab <id>' },
|
|
'newtab': { category: 'Tabs', description: 'Open new tab', usage: 'newtab [url]' },
|
|
'closetab':{ category: 'Tabs', description: 'Close tab', usage: 'closetab [id]' },
|
|
// Server
|
|
'status': { category: 'Server', description: 'Health check' },
|
|
'stop': { category: 'Server', description: 'Shutdown server' },
|
|
'restart': { category: 'Server', description: 'Restart server' },
|
|
// Meta
|
|
'snapshot':{ category: 'Snapshot', description: 'Accessibility tree with @refs', usage: 'snapshot [flags]' },
|
|
'chain': { category: 'Meta', description: 'Multi-command from JSON stdin' },
|
|
};
|
|
|
|
// Load-time validation: descriptions must cover exactly the command sets
|
|
const allCmds = new Set([...READ_COMMANDS, ...WRITE_COMMANDS, ...META_COMMANDS]);
|
|
const descKeys = new Set(Object.keys(COMMAND_DESCRIPTIONS));
|
|
for (const cmd of allCmds) {
|
|
if (!descKeys.has(cmd)) throw new Error(`COMMAND_DESCRIPTIONS missing entry for: ${cmd}`);
|
|
}
|
|
for (const key of descKeys) {
|
|
if (!allCmds.has(key)) throw new Error(`COMMAND_DESCRIPTIONS has unknown command: ${key}`);
|
|
}
|