Loading...
Loading...
Codify the most recent successful /scrape flow into a permanent browser-skill on disk. Future /scrape calls with the same intent run the codified script in ~200ms instead of re-driving the page. Walks back through the conversation, synthesizes script.ts + script.test.ts + fixture, runs the test in a temp dir, and asks before committing. Use when asked to "skillify", "codify", "save this scrape", or "make this permanent". (gstack)
npx skill4agent add garrytan/gstack skillify_UPD=$(~/.claude/skills/gstack/bin/gstack-update-check 2>/dev/null || .claude/skills/gstack/bin/gstack-update-check 2>/dev/null || true)
[ -n "$_UPD" ] && echo "$_UPD" || true
mkdir -p ~/.gstack/sessions
touch ~/.gstack/sessions/"$PPID"
_SESSIONS=$(find ~/.gstack/sessions -mmin -120 -type f 2>/dev/null | wc -l | tr -d ' ')
find ~/.gstack/sessions -mmin +120 -type f -exec rm {} + 2>/dev/null || true
_PROACTIVE=$(~/.claude/skills/gstack/bin/gstack-config get proactive 2>/dev/null || echo "true")
_PROACTIVE_PROMPTED=$([ -f ~/.gstack/.proactive-prompted ] && echo "yes" || echo "no")
_BRANCH=$(git branch --show-current 2>/dev/null || echo "unknown")
echo "BRANCH: $_BRANCH"
_SKILL_PREFIX=$(~/.claude/skills/gstack/bin/gstack-config get skill_prefix 2>/dev/null || echo "false")
echo "PROACTIVE: $_PROACTIVE"
echo "PROACTIVE_PROMPTED: $_PROACTIVE_PROMPTED"
echo "SKILL_PREFIX: $_SKILL_PREFIX"
source <(~/.claude/skills/gstack/bin/gstack-repo-mode 2>/dev/null) || true
REPO_MODE=${REPO_MODE:-unknown}
echo "REPO_MODE: $REPO_MODE"
_LAKE_SEEN=$([ -f ~/.gstack/.completeness-intro-seen ] && echo "yes" || echo "no")
echo "LAKE_INTRO: $_LAKE_SEEN"
_TEL=$(~/.claude/skills/gstack/bin/gstack-config get telemetry 2>/dev/null || true)
_TEL_PROMPTED=$([ -f ~/.gstack/.telemetry-prompted ] && echo "yes" || echo "no")
_TEL_START=$(date +%s)
_SESSION_ID="$$-$(date +%s)"
echo "TELEMETRY: ${_TEL:-off}"
echo "TEL_PROMPTED: $_TEL_PROMPTED"
_EXPLAIN_LEVEL=$(~/.claude/skills/gstack/bin/gstack-config get explain_level 2>/dev/null || echo "default")
if [ "$_EXPLAIN_LEVEL" != "default" ] && [ "$_EXPLAIN_LEVEL" != "terse" ]; then _EXPLAIN_LEVEL="default"; fi
echo "EXPLAIN_LEVEL: $_EXPLAIN_LEVEL"
_QUESTION_TUNING=$(~/.claude/skills/gstack/bin/gstack-config get question_tuning 2>/dev/null || echo "false")
echo "QUESTION_TUNING: $_QUESTION_TUNING"
mkdir -p ~/.gstack/analytics
if [ "$_TEL" != "off" ]; then
echo '{"skill":"skillify","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true
fi
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
if [ -f "$_PF" ]; then
if [ "$_TEL" != "off" ] && [ -x "~/.claude/skills/gstack/bin/gstack-telemetry-log" ]; then
~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown --session-id "$_SESSION_ID" 2>/dev/null || true
fi
rm -f "$_PF" 2>/dev/null || true
fi
break
done
eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" 2>/dev/null || true
_LEARN_FILE="${GSTACK_HOME:-$HOME/.gstack}/projects/${SLUG:-unknown}/learnings.jsonl"
if [ -f "$_LEARN_FILE" ]; then
_LEARN_COUNT=$(wc -l < "$_LEARN_FILE" 2>/dev/null | tr -d ' ')
echo "LEARNINGS: $_LEARN_COUNT entries loaded"
if [ "$_LEARN_COUNT" -gt 5 ] 2>/dev/null; then
~/.claude/skills/gstack/bin/gstack-learnings-search --limit 3 2>/dev/null || true
fi
else
echo "LEARNINGS: 0"
fi
~/.claude/skills/gstack/bin/gstack-timeline-log '{"skill":"skillify","event":"started","branch":"'"$_BRANCH"'","session":"'"$_SESSION_ID"'"}' 2>/dev/null &
_HAS_ROUTING="no"
if [ -f CLAUDE.md ] && grep -q "## Skill routing" CLAUDE.md 2>/dev/null; then
_HAS_ROUTING="yes"
fi
_ROUTING_DECLINED=$(~/.claude/skills/gstack/bin/gstack-config get routing_declined 2>/dev/null || echo "false")
echo "HAS_ROUTING: $_HAS_ROUTING"
echo "ROUTING_DECLINED: $_ROUTING_DECLINED"
_VENDORED="no"
if [ -d ".claude/skills/gstack" ] && [ ! -L ".claude/skills/gstack" ]; then
if [ -f ".claude/skills/gstack/VERSION" ] || [ -d ".claude/skills/gstack/.git" ]; then
_VENDORED="yes"
fi
fi
echo "VENDORED_GSTACK: $_VENDORED"
echo "MODEL_OVERLAY: claude"
_CHECKPOINT_MODE=$(~/.claude/skills/gstack/bin/gstack-config get checkpoint_mode 2>/dev/null || echo "explicit")
_CHECKPOINT_PUSH=$(~/.claude/skills/gstack/bin/gstack-config get checkpoint_push 2>/dev/null || echo "false")
echo "CHECKPOINT_MODE: $_CHECKPOINT_MODE"
echo "CHECKPOINT_PUSH: $_CHECKPOINT_PUSH"
[ -n "$OPENCLAW_SESSION" ] && echo "SPAWNED_SESSION: true" || true$B$Dcodex execcodex review~/.gstack/openmcp__*__AskUserQuestion## Decisions to confirmPROACTIVE"false"SKILL_PREFIX"true"/gstack-*~/.claude/skills/gstack/[skill-name]/SKILL.mdUPGRADE_AVAILABLE <old> <new>~/.claude/skills/gstack/gstack-upgrade/SKILL.mdJUST_UPGRADED <from> <to>SPAWNED_SESSION~/.claude/skills/gstack/.feature-prompted-continuous-checkpoint~/.claude/skills/gstack/bin/gstack-config set checkpoint_mode continuous~/.claude/skills/gstack/.feature-prompted-model-overlayWRITING_STYLE_PENDINGyesv1 prompts are simpler: first-use jargon glosses, outcome-framed questions, shorter prose. Keep default or restore terse?
explain_level: terseexplain_leveldefault~/.claude/skills/gstack/bin/gstack-config set explain_level terserm -f ~/.gstack/.writing-style-prompt-pending
touch ~/.gstack/.writing-style-promptedWRITING_STYLE_PENDINGnoLAKE_INTROnoopen https://garryslist.org/posts/boil-the-ocean
touch ~/.gstack/.completeness-intro-seenopentouchTEL_PROMPTEDnoLAKE_INTROyesHelp gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code, file paths, or repo names.
~/.claude/skills/gstack/bin/gstack-config set telemetry communityAnonymous mode sends only aggregate usage, no unique ID.
~/.claude/skills/gstack/bin/gstack-config set telemetry anonymous~/.claude/skills/gstack/bin/gstack-config set telemetry offtouch ~/.gstack/.telemetry-promptedTEL_PROMPTEDyesPROACTIVE_PROMPTEDnoTEL_PROMPTEDyesLet gstack proactively suggest skills, like /qa for "does this work?" or /investigate for bugs?
~/.claude/skills/gstack/bin/gstack-config set proactive true~/.claude/skills/gstack/bin/gstack-config set proactive falsetouch ~/.gstack/.proactive-promptedPROACTIVE_PROMPTEDyesHAS_ROUTINGnoROUTING_DECLINEDfalsePROACTIVE_PROMPTEDyesgstack works best when your project's CLAUDE.md includes skill routing rules.
## Skill routing
When the user's request matches an available skill, invoke it via the Skill tool. When in doubt, invoke the skill.
Key routing rules:
- Product ideas/brainstorming → invoke /office-hours
- Strategy/scope → invoke /plan-ceo-review
- Architecture → invoke /plan-eng-review
- Design system/plan review → invoke /design-consultation or /plan-design-review
- Full review pipeline → invoke /autoplan
- Bugs/errors → invoke /investigate
- QA/testing site behavior → invoke /qa or /qa-only
- Code review/diff check → invoke /review
- Visual polish → invoke /design-review
- Ship/deploy/PR → invoke /ship or /land-and-deploy
- Save progress → invoke /context-save
- Resume context → invoke /context-restoregit add CLAUDE.md && git commit -m "chore: add gstack skill routing rules to CLAUDE.md"~/.claude/skills/gstack/bin/gstack-config set routing_declined truegstack-config set routing_declined falseHAS_ROUTINGyesROUTING_DECLINEDtrueVENDORED_GSTACKyes~/.gstack/.vendoring-warned-$SLUGThis project has gstack vendored in. Vendoring is deprecated. Migrate to team mode?.claude/skills/gstack/
git rm -r .claude/skills/gstack/echo '.claude/skills/gstack/' >> .gitignore~/.claude/skills/gstack/bin/gstack-team-init requiredoptionalgit add .claude/ .gitignore CLAUDE.md && git commit -m "chore: migrate gstack from vendored to team mode"cd ~/.claude/skills/gstack && ./setup --teameval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" 2>/dev/null || true
touch ~/.gstack/.vendoring-warned-${SLUG:-unknown}SPAWNED_SESSION"true"mcp__conductor__AskUserQuestionmcp__*__AskUserQuestion--disallowedTools AskUserQuestion## Decisions to confirm/plan-tuneD<N> — <one-line question title>
Project/branch/task: <1 short grounding sentence using _BRANCH>
ELI10: <plain English a 16-year-old could follow, 2-4 sentences, name the stakes>
Stakes if we pick wrong: <one sentence on what breaks, what user sees, what's lost>
Recommendation: <choice> because <one-line reason>
Completeness: A=X/10, B=Y/10 (or: Note: options differ in kind, not coverage — no completeness score)
Pros / cons:
A) <option label> (recommended)
✅ <pro — concrete, observable, ≥40 chars>
❌ <con — honest, ≥40 chars>
B) <option label>
✅ <pro>
❌ <con>
Net: <one-line synthesis of what you're actually trading off>D1(recommended)Completeness: N/10Note: options differ in kind, not coverage — no completeness score.✅ No cons — this is a hard-stop choiceRecommendation: <default> — this is a taste call, no strong preference either way(recommended)(human: ~2 days / CC: ~15 min)_GSTACK_HOME="${GSTACK_HOME:-$HOME/.gstack}"
# Prefer the v1.27.0.0 artifacts file; fall back to brain file for users
# upgrading mid-stream before the migration script runs.
if [ -f "$HOME/.gstack-artifacts-remote.txt" ]; then
_BRAIN_REMOTE_FILE="$HOME/.gstack-artifacts-remote.txt"
else
_BRAIN_REMOTE_FILE="$HOME/.gstack-brain-remote.txt"
fi
_BRAIN_SYNC_BIN="~/.claude/skills/gstack/bin/gstack-brain-sync"
_BRAIN_CONFIG_BIN="~/.claude/skills/gstack/bin/gstack-config"
# /sync-gbrain context-load: teach the agent to use gbrain when it's available.
# Mutually exclusive variants per /plan-eng-review §4. Empty string when gbrain
# is not configured (zero context cost for non-gbrain users).
_GBRAIN_CONFIG="$HOME/.gbrain/config.json"
if [ -f "$_GBRAIN_CONFIG" ] && command -v gbrain >/dev/null 2>&1; then
_GBRAIN_VERSION_OK=$(gbrain --version 2>/dev/null | grep -c '^gbrain ' || echo 0)
if [ "$_GBRAIN_VERSION_OK" -gt 0 ] 2>/dev/null; then
_SYNC_STATE="$_GSTACK_HOME/.gbrain-sync-state.json"
_CWD_PAGES=0
if [ -f "$_SYNC_STATE" ]; then
# Flatten newlines so the regex works against pretty-printed JSON too.
_CWD_PAGES=$(tr -d '\n' < "$_SYNC_STATE" 2>/dev/null \
| grep -o '"name": *"code"[^}]*"detail": *{[^}]*"page_count": *[0-9]*' \
| grep -o '"page_count": *[0-9]*' | grep -o '[0-9]\+' | head -1)
_CWD_PAGES=${_CWD_PAGES:-0}
fi
if [ "$_CWD_PAGES" -gt 0 ] 2>/dev/null; then
echo "GBrain configured. Prefer \`gbrain search\`/\`gbrain query\` over Grep for"
echo "semantic questions; use \`gbrain code-def\`/\`code-refs\`/\`code-callers\` for"
echo "symbol-aware code lookup. See \"## GBrain Search Guidance\" in CLAUDE.md."
echo "Run /sync-gbrain to refresh."
else
echo "GBrain configured but this repo isn't indexed yet. Run \`/sync-gbrain --full\`"
echo "before relying on \`gbrain search\` for code questions in this repo."
echo "Falls back to Grep until indexed."
fi
fi
fi
_BRAIN_SYNC_MODE=$("$_BRAIN_CONFIG_BIN" get artifacts_sync_mode 2>/dev/null || echo off)
# Detect remote-MCP mode (Path 4 of /setup-gbrain). Local artifacts sync is
# a no-op in remote mode; the brain server pulls from GitHub/GitLab on its
# own cadence. Read claude.json directly to keep this preamble fast (no
# subprocess to claude CLI on every skill start).
_GBRAIN_MCP_MODE="none"
if command -v jq >/dev/null 2>&1 && [ -f "$HOME/.claude.json" ]; then
_GBRAIN_MCP_TYPE=$(jq -r '.mcpServers.gbrain.type // .mcpServers.gbrain.transport // empty' "$HOME/.claude.json" 2>/dev/null)
case "$_GBRAIN_MCP_TYPE" in
url|http|sse) _GBRAIN_MCP_MODE="remote-http" ;;
stdio) _GBRAIN_MCP_MODE="local-stdio" ;;
esac
fi
if [ -f "$_BRAIN_REMOTE_FILE" ] && [ ! -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" = "off" ]; then
_BRAIN_NEW_URL=$(head -1 "$_BRAIN_REMOTE_FILE" 2>/dev/null | tr -d '[:space:]')
if [ -n "$_BRAIN_NEW_URL" ]; then
echo "ARTIFACTS_SYNC: artifacts repo detected: $_BRAIN_NEW_URL"
echo "ARTIFACTS_SYNC: run 'gstack-brain-restore' to pull your cross-machine artifacts (or 'gstack-config set artifacts_sync_mode off' to dismiss forever)"
fi
fi
if [ -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" != "off" ]; then
_BRAIN_LAST_PULL_FILE="$_GSTACK_HOME/.brain-last-pull"
_BRAIN_NOW=$(date +%s)
_BRAIN_DO_PULL=1
if [ -f "$_BRAIN_LAST_PULL_FILE" ]; then
_BRAIN_LAST=$(cat "$_BRAIN_LAST_PULL_FILE" 2>/dev/null || echo 0)
_BRAIN_AGE=$(( _BRAIN_NOW - _BRAIN_LAST ))
[ "$_BRAIN_AGE" -lt 86400 ] && _BRAIN_DO_PULL=0
fi
if [ "$_BRAIN_DO_PULL" = "1" ]; then
( cd "$_GSTACK_HOME" && git fetch origin >/dev/null 2>&1 && git merge --ff-only "origin/$(git rev-parse --abbrev-ref HEAD)" >/dev/null 2>&1 ) || true
echo "$_BRAIN_NOW" > "$_BRAIN_LAST_PULL_FILE"
fi
"$_BRAIN_SYNC_BIN" --once 2>/dev/null || true
fi
if [ "$_GBRAIN_MCP_MODE" = "remote-http" ]; then
# Remote-MCP mode: local artifacts sync is a no-op (brain admin's server
# pulls from GitHub/GitLab). Show the user this is by design, not broken.
_GBRAIN_HOST=$(jq -r '.mcpServers.gbrain.url // empty' "$HOME/.claude.json" 2>/dev/null | sed -E 's|^https?://([^/:]+).*|\1|')
echo "ARTIFACTS_SYNC: remote-mode (managed by brain server ${_GBRAIN_HOST:-remote})"
elif [ -d "$_GSTACK_HOME/.git" ] && [ "$_BRAIN_SYNC_MODE" != "off" ]; then
_BRAIN_QUEUE_DEPTH=0
[ -f "$_GSTACK_HOME/.brain-queue.jsonl" ] && _BRAIN_QUEUE_DEPTH=$(wc -l < "$_GSTACK_HOME/.brain-queue.jsonl" | tr -d ' ')
_BRAIN_LAST_PUSH="never"
[ -f "$_GSTACK_HOME/.brain-last-push" ] && _BRAIN_LAST_PUSH=$(cat "$_GSTACK_HOME/.brain-last-push" 2>/dev/null || echo never)
echo "ARTIFACTS_SYNC: mode=$_BRAIN_SYNC_MODE | last_push=$_BRAIN_LAST_PUSH | queue=$_BRAIN_QUEUE_DEPTH"
else
echo "ARTIFACTS_SYNC: off"
fiARTIFACTS_SYNC: offartifacts_sync_mode_promptedfalsegbrain doctor --fast --jsongstack can publish your artifacts (CEO plans, designs, reports) to a private GitHub repo that GBrain indexes across machines. How much should sync?
# Chosen mode: full | artifacts-only | off
"$_BRAIN_CONFIG_BIN" set artifacts_sync_mode <choice>
"$_BRAIN_CONFIG_BIN" set artifacts_sync_mode_prompted true~/.gstack/.gitgstack-artifacts-init"~/.claude/skills/gstack/bin/gstack-brain-sync" --discover-new 2>/dev/null || true
"~/.claude/skills/gstack/bin/gstack-brain-sync" --once 2>/dev/null || trueeval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)"
_PROJ="${GSTACK_HOME:-$HOME/.gstack}/projects/${SLUG:-unknown}"
if [ -d "$_PROJ" ]; then
echo "--- RECENT ARTIFACTS ---"
find "$_PROJ/ceo-plans" "$_PROJ/checkpoints" -type f -name "*.md" 2>/dev/null | xargs ls -t 2>/dev/null | head -3
[ -f "$_PROJ/${_BRANCH}-reviews.jsonl" ] && echo "REVIEWS: $(wc -l < "$_PROJ/${_BRANCH}-reviews.jsonl" | tr -d ' ') entries"
[ -f "$_PROJ/timeline.jsonl" ] && tail -5 "$_PROJ/timeline.jsonl"
if [ -f "$_PROJ/timeline.jsonl" ]; then
_LAST=$(grep "\"branch\":\"${_BRANCH}\"" "$_PROJ/timeline.jsonl" 2>/dev/null | grep '"event":"completed"' | tail -1)
[ -n "$_LAST" ] && echo "LAST_SESSION: $_LAST"
_RECENT_SKILLS=$(grep "\"branch\":\"${_BRANCH}\"" "$_PROJ/timeline.jsonl" 2>/dev/null | grep '"event":"completed"' | tail -3 | grep -o '"skill":"[^"]*"' | sed 's/"skill":"//;s/"//' | tr '\n' ',')
[ -n "$_RECENT_SKILLS" ] && echo "RECENT_PATTERN: $_RECENT_SKILLS"
fi
_LATEST_CP=$(find "$_PROJ/checkpoints" -name "*.md" -type f 2>/dev/null | xargs ls -t 2>/dev/null | head -1)
[ -n "$_LATEST_CP" ] && echo "LATEST_CHECKPOINT: $_LATEST_CP"
echo "--- END ARTIFACTS ---"
fiLAST_SESSIONLATEST_CHECKPOINTRECENT_PATTERNEXPLAIN_LEVEL: terseCompleteness: X/10Note: options differ in kind, not coverage — no completeness score.CHECKPOINT_MODE"continuous"WIP:WIP: <concise description of what changed>
[gstack-context]
Decisions: <key choices made this step>
Remaining: <what's left in the logical unit>
Tried: <failed approaches worth recording> (omit if none)
Skill: </skill-name-if-running>
[/gstack-context]git add -ACHECKPOINT_PUSH"true"/context-restore[gstack-context]/shipCHECKPOINT_MODE"explicit"[PROGRESS]QUESTION_TUNING: falsequestion_idscripts/question-registry.ts{skill}-{slug}~/.claude/skills/gstack/bin/gstack-question-preference --check "<id>"AUTO_DECIDEASK_NORMALLY~/.claude/skills/gstack/bin/gstack-question-log '{"skill":"skillify","question_id":"<id>","question_summary":"<short>","category":"<approval|clarification|routing|cherry-pick|feedback-loop>","door_type":"<one-way|two-way>","options_count":N,"user_choice":"<key>","recommended":"<key>","session_id":"'"$_SESSION_ID"'"}' 2>/dev/null || truetune: never-asktune: always-asktune:~/.claude/skills/gstack/bin/gstack-question-preference --write '{"question_id":"<id>","preference":"<pref>","source":"inline-user","free_text":"<optional original words>"}'<id><preference>REPO_MODEsolocollaborativeunknown~/.claude/skills/gstack/ETHOS.mdjq -n --arg ts "$(date -u +%Y-%m-%dT%H:%M:%SZ)" --arg skill "SKILL_NAME" --arg branch "$(git branch --show-current 2>/dev/null)" --arg insight "ONE_LINE_SUMMARY" '{ts:$ts,skill:$skill,branch:$branch,insight:$insight}' >> ~/.gstack/analytics/eureka.jsonl 2>/dev/null || trueSTATUSREASONATTEMPTEDRECOMMENDATION~/.claude/skills/gstack/bin/gstack-learnings-log '{"skill":"SKILL_NAME","type":"operational","key":"SHORT_KEY","insight":"DESCRIPTION","confidence":N,"source":"observed"}'name:~/.gstack/analytics/_TEL_END=$(date +%s)
_TEL_DUR=$(( _TEL_END - _TEL_START ))
rm -f ~/.gstack/analytics/.pending-"$_SESSION_ID" 2>/dev/null || true
# Session timeline: record skill completion (local-only, never sent anywhere)
~/.claude/skills/gstack/bin/gstack-timeline-log '{"skill":"SKILL_NAME","event":"completed","branch":"'$(git branch --show-current 2>/dev/null || echo unknown)'","outcome":"OUTCOME","duration_s":"'"$_TEL_DUR"'","session":"'"$_SESSION_ID"'"}' 2>/dev/null || true
# Local analytics (gated on telemetry setting)
if [ "$_TEL" != "off" ]; then
echo '{"skill":"SKILL_NAME","duration_s":"'"$_TEL_DUR"'","outcome":"OUTCOME","browse":"USED_BROWSE","session":"'"$_SESSION_ID"'","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true
fi
# Remote telemetry (opt-in, requires binary)
if [ "$_TEL" != "off" ] && [ -x ~/.claude/skills/gstack/bin/gstack-telemetry-log ]; then
~/.claude/skills/gstack/bin/gstack-telemetry-log \
--skill "SKILL_NAME" --duration "$_TEL_DUR" --outcome "OUTCOME" \
--used-browse "USED_BROWSE" --session-id "$_SESSION_ID" 2>/dev/null &
fiSKILL_NAMEOUTCOMEUSED_BROWSE## GSTACK REVIEW REPORT~/.claude/skills/gstack/bin/gstack-review-readNO_REVIEWS/autoplan/scrape/skillifybrowse-client/scrape/scrape$B$B skill list/scrape"No recent /scrape result found in this conversation. Run /scrape <intent> first, then say /skillify."
"The last successful /scrape was '<intent line>' a few turns back. Skillify that one?"
lobsters-frontpagegh-issue-listpypi-package-stats/scrapelobste.rsD<N> — Skill name + tier
Project/branch/task: codifying /scrape "<intent>" as a browser-skill.
ELI10: Pick a short name we'll use to find this skill next time you say
something similar. Pick a tier — global means every project on this
machine sees it, project means just this repo.
Stakes if we pick wrong: bad name buries the skill in $B skill list;
wrong tier means future projects can't find it (or can find it when you
didn't want them to).
Recommendation: A — <proposed-name> at global tier — most scrape skills
generalize across projects.
Note: options differ in kind, not coverage — no completeness score.
A) Keep "<proposed-name>" at global tier — ~/.gstack/browser-skills/<proposed-name>/ (recommended)
B) Keep "<proposed-name>" but at project tier — <project>/.gstack/browser-skills/<proposed-name>/
C) Rename it (free-form — say the new name)$B skill list"Note: a <tier> skill named '<name>' already exists. Picking the same name at a higher tier (project > global > bundled) shadows it; picking the same tier collides and will be refused at write time. Pick a different name to coexist."
script.ts$B$B./_lib/browse-clientscript.test.tsbrowser-skills/hackernews-frontpage/script.tsimport { browse } from './_lib/browse-client';
export interface Item { /* one row of the JSON output */ }
export interface Output { items: Item[]; count: number; }
const TARGET_URL = '<the URL the prototype used>';
export function parseFromHtml(html: string): Item[] {
// Pure function: HTML in, parsed Item[] out. No $B calls.
// Future fixture-replay tests call this directly.
}
if (import.meta.main) { await main(); }
async function main(): Promise<void> {
await browse.goto(TARGET_URL);
const html = await browse.html();
const items = parseFromHtml(html);
const output: Output = { items, count: items.length };
process.stdout.write(JSON.stringify(output) + '\n');
}$Bmain()$B goto "<TARGET_URL>"
$B html > /tmp/skillify-fixture-$$.htmlfixtures/<host-with-dashes>-<YYYY-MM-DD>.htmlfixtures/lobste-rs-2026-04-27.htmlscript.test.tsbrowser-skills/hackernews-frontpage/script.test.tsparseFromHtmlimport { describe, it, expect } from 'bun:test';
import * as fs from 'fs';
import * as path from 'path';
import { parseFromHtml } from './script';
describe('<name> parser', () => {
const fixturePath = path.join(import.meta.dir, 'fixtures', '<host>-<date>.html');
const html = fs.readFileSync(fixturePath, 'utf-8');
const items = parseFromHtml(html);
it('returns at least one item from the bundled fixture', () => {
expect(items.length).toBeGreaterThan(0);
});
it('every item has the required shape', () => {
for (const item of items) {
expect(typeof item.<keyfield>).toBe('<keytype>');
// ... assert on every required field
}
});
});<gstack-install>/browse/src/browse-client.tshackernews-frontpage$B skill listbundled<gstack-install>/browser-skills/hackernews-frontpage/dirname_lib/browse-client.ts~/.claude/skills/gstack/import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
function resolveSdkPath(): string {
const candidates = [
path.join(os.homedir(), '.claude', 'skills', 'gstack', 'browse', 'src', 'browse-client.ts'),
// Add other install-dir candidates if your environment differs.
];
for (const c of candidates) {
try {
const real = fs.realpathSync(c);
if (fs.existsSync(real)) return real;
} catch {}
}
throw new Error('Could not resolve canonical browse-client.ts');
}
const sdkContents = fs.readFileSync(resolveSdkPath(), 'utf-8');_lib/browse-client.tsbrowse/src/browser-skill-write.tsimport { stageSkill } from '<gstack-install>/browse/src/browser-skill-write';
const stagedDir = stageSkill({
name: '<name>',
files: new Map([
['SKILL.md', skillMd],
['script.ts', scriptTs],
['script.test.ts', scriptTestTs],
['_lib/browse-client.ts', sdkContents],
['fixtures/<host>-<date>.html', fixtureHtml],
]),
});
console.log(stagedDir);<name>---
name: <name>
description: <one-line, what data this returns>
host: <hostname>
trusted: false # agent-authored skills are untrusted by default
source: agent
version: 1.0.0
args: [] # extend if your script accepts --arg key=value
triggers:
- <phrase 1>
- <phrase 2>
- <phrase 3>
---
# <Name> scraper
<2-3 sentences on what the script does, what URL it hits, and what
shape of JSON it returns. NO conversation context. NO chat fragments.
This is a durable on-disk artifact — keep it tight.>
## Usage
\`\`\`
$ $B skill run <name>
{ "items": [...], "count": N }
\`\`\`stagedDirstageSkill$B skill testcommitSkilldiscardStaged$B skill test$B skill test "<name>" --dir "<stagedDir>"$B skill test--dir( cd "<stagedDir>" && bun test script.test.ts )script.tsscript.test.tsimport { discardStaged } from '<gstack-install>/browse/src/browser-skill-write';
discardStaged('<stagedDir>');script.tsD<N> — Commit skill "<name>" at <resolved-tier-path>?
Project/branch/task: codified /scrape "<intent>" — tests pass against fixture.
ELI10: The script ran clean against the snapshot we captured. Saying yes
moves the staged folder into ~/.gstack/browser-skills/ where /scrape
will find it next time. Saying no removes the staged folder and nothing
lands on disk.
Stakes if we pick wrong: yes commits an artifact you have to manually rm
later if you regret it ($B skill rm <name> --global). No throws away
~30s of synthesis work.
Recommendation: A — tests passed, the script is self-contained, this is
the productivity payoff for the prototype.
Note: options differ in kind, not coverage — no completeness score.
A) Commit it (recommended)
B) Look at the script first (I'll print SKILL.md + script.ts and re-ask)
C) Discard — don't commitSKILL.mdscript.tsimport { commitSkill } from '<gstack-install>/browse/src/browser-skill-write';
const dest = commitSkill({
name: '<name>',
tier: '<global|project>', // from step 2 answer
stagedDir: '<stagedDir>',
});
console.log(`Committed: ${dest}`);commitSkill$B skill rm <name>import { discardStaged } from '<gstack-install>/browse/src/browser-skill-write';
discardStaged('<stagedDir>');$B skill list | grep <name>
$B skill run <name> # should match the JSON the prototype produced$B skill rm <name>bun run script.ts$B gotoargs:$B skill run$B skill show <name>~/.claude/skills/gstack/bin/gstack-learnings-log '{"skill":"skillify","type":"TYPE","key":"SHORT_KEY","insight":"DESCRIPTION","confidence":N,"source":"SOURCE","files":["path/to/relevant/file"]}'patternpitfallpreferencearchitecturetooloperationalobserveduser-statedinferredcross-model