mirror of https://github.com/garrytan/gstack.git
feat: first-run activation — project-aware scaffold, router front door, onboarding nudges
Adds the activation system that drives a new install toward a concrete first move: - bin/gstack-first-task-detect: local-git+filesystem repo classifier emitting one validated enum bucket (greenfield/code_<lang>/branch_ahead/dirty_default/clean_default), portable timeouts, fail-safe empty output. - generate-first-run-guidance.ts: unified preamble section — first-run project-aware scaffold + returning-session plan->review->ship tip, gated on a persistent .activated marker and never run in headless. Detection wired lazily in generate-preamble-bash.ts. - SKILL.md.tmpl: top-level gstack skill is now a pure router (browse body removed; it lives in /browse), routing any request and sending browser/QA work to /browse. - setup: first-move nudge on first install. office-hours: closing handoff that launches the next review via the Skill tool. - telemetry-ingest: accept onboarding/first_task_scaffold_shown/handoff/route event types.
This commit is contained in:
parent
a861c00cfa
commit
938fa4a035
265
SKILL.md.tmpl
265
SKILL.md.tmpl
|
|
@ -1,26 +1,40 @@
|
|||
---
|
||||
name: gstack
|
||||
preamble-tier: 1
|
||||
version: 1.1.0
|
||||
version: 1.2.0
|
||||
description: |
|
||||
Fast headless browser for QA testing and site dogfooding. Navigate pages, interact with
|
||||
elements, verify state, diff before/after, take annotated screenshots, test responsive
|
||||
layouts, forms, uploads, dialogs, and capture bug evidence. Use when asked to open or
|
||||
test a site, verify a deployment, dogfood a user flow, or file a bug with screenshots. (gstack)
|
||||
Router for the gstack skill suite. Sends any gstack request to the right skill
|
||||
(planning, review, QA, shipping, debugging, docs, security, design). For browser/QA
|
||||
and dogfooding it points you at /browse. Use when you invoke gstack without a specific
|
||||
skill, or ask "which gstack skill fits this?". (gstack)
|
||||
allowed-tools:
|
||||
- Bash
|
||||
- Read
|
||||
- AskUserQuestion
|
||||
triggers:
|
||||
- browse this page
|
||||
- take a screenshot
|
||||
- navigate to url
|
||||
- inspect the page
|
||||
- gstack
|
||||
- which gstack skill
|
||||
- route this with gstack
|
||||
|
||||
---
|
||||
|
||||
{{PREAMBLE}}
|
||||
|
||||
## Route first
|
||||
|
||||
This is the gstack router. Its one job is to send the request to the right skill.
|
||||
|
||||
1. If the request is about a browser, QA, dogfooding, screenshots, or inspecting a page
|
||||
(open a site, test a deploy, take a screenshot, check a flow visually) → invoke `/browse`.
|
||||
2. Otherwise, route by the rules below. If nothing matches, answer directly.
|
||||
|
||||
Best-effort, record which way you routed (never block on it). Set `ROUTE_OUTCOME` to
|
||||
`browse` (sent to /browse), `routed` (sent to another skill), or `direct` (answered
|
||||
directly, no skill matched):
|
||||
```bash
|
||||
~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type route --skill gstack --outcome ROUTE_OUTCOME --session-id "$_SESSION_ID" 2>/dev/null || true
|
||||
```
|
||||
|
||||
If `PROACTIVE` is `false`: do NOT proactively invoke or suggest other gstack skills during
|
||||
this session. Only run skills the user explicitly invokes. This preference persists across
|
||||
sessions via `gstack-config`.
|
||||
|
|
@ -75,236 +89,3 @@ directly as usual.
|
|||
|
||||
If the user opts out of suggestions, run `gstack-config set proactive false`.
|
||||
If they opt back in, run `gstack-config set proactive true`.
|
||||
|
||||
# gstack browse: QA Testing & Dogfooding
|
||||
|
||||
Persistent headless Chromium. First call auto-starts (~3s), then ~100-200ms per command.
|
||||
Auto-shuts down after 30 min idle. State persists between calls (cookies, tabs, sessions).
|
||||
|
||||
{{BROWSE_SETUP}}
|
||||
|
||||
## IMPORTANT
|
||||
|
||||
- Use the compiled binary via Bash: `$B <command>`
|
||||
- NEVER use `mcp__claude-in-chrome__*` tools. They are slow and unreliable.
|
||||
- Browser persists between calls — cookies, login sessions, and tabs carry over.
|
||||
- Dialogs (alert/confirm/prompt) are auto-accepted by default — no browser lockup.
|
||||
- **Show screenshots:** After `$B screenshot`, `$B snapshot -a -o`, or `$B responsive`, always use the Read tool on the output PNG(s) so the user can see them. Without this, screenshots are invisible.
|
||||
|
||||
## QA Workflows
|
||||
|
||||
> **Credential safety:** Use environment variables for test credentials.
|
||||
> Set them before running: `export TEST_EMAIL="..." TEST_PASSWORD="..."`
|
||||
|
||||
### Test a user flow (login, signup, checkout, etc.)
|
||||
|
||||
```bash
|
||||
# 1. Go to the page
|
||||
$B goto https://app.example.com/login
|
||||
|
||||
# 2. See what's interactive
|
||||
$B snapshot -i
|
||||
|
||||
# 3. Fill the form using refs
|
||||
$B fill @e3 "$TEST_EMAIL"
|
||||
$B fill @e4 "$TEST_PASSWORD"
|
||||
$B click @e5
|
||||
|
||||
# 4. Verify it worked
|
||||
$B snapshot -D # diff shows what changed after clicking
|
||||
$B is visible ".dashboard" # assert the dashboard appeared
|
||||
$B screenshot /tmp/after-login.png
|
||||
```
|
||||
|
||||
### Verify a deployment / check prod
|
||||
|
||||
```bash
|
||||
$B goto https://yourapp.com
|
||||
$B text # read the page — does it load?
|
||||
$B console # any JS errors?
|
||||
$B network # any failed requests?
|
||||
$B js "document.title" # correct title?
|
||||
$B is visible ".hero-section" # key elements present?
|
||||
$B screenshot /tmp/prod-check.png
|
||||
```
|
||||
|
||||
### Dogfood a feature end-to-end
|
||||
|
||||
```bash
|
||||
# Navigate to the feature
|
||||
$B goto https://app.example.com/new-feature
|
||||
|
||||
# Take annotated screenshot — shows every interactive element with labels
|
||||
$B snapshot -i -a -o /tmp/feature-annotated.png
|
||||
|
||||
# Find ALL clickable things (including divs with cursor:pointer)
|
||||
$B snapshot -C
|
||||
|
||||
# Walk through the flow
|
||||
$B snapshot -i # baseline
|
||||
$B click @e3 # interact
|
||||
$B snapshot -D # what changed? (unified diff)
|
||||
|
||||
# Check element states
|
||||
$B is visible ".success-toast"
|
||||
$B is enabled "#next-step-btn"
|
||||
$B is checked "#agree-checkbox"
|
||||
|
||||
# Check console for errors after interactions
|
||||
$B console
|
||||
```
|
||||
|
||||
### Test responsive layouts
|
||||
|
||||
```bash
|
||||
# Quick: 3 screenshots at mobile/tablet/desktop
|
||||
$B goto https://yourapp.com
|
||||
$B responsive /tmp/layout
|
||||
|
||||
# Manual: specific viewport
|
||||
$B viewport 375x812 # iPhone
|
||||
$B screenshot /tmp/mobile.png
|
||||
$B viewport 1440x900 # Desktop
|
||||
$B screenshot /tmp/desktop.png
|
||||
|
||||
# Element screenshot (crop to specific element)
|
||||
$B screenshot "#hero-banner" /tmp/hero.png
|
||||
$B snapshot -i
|
||||
$B screenshot @e3 /tmp/button.png
|
||||
|
||||
# Region crop
|
||||
$B screenshot --clip 0,0,800,600 /tmp/above-fold.png
|
||||
|
||||
# Viewport only (no scroll)
|
||||
$B screenshot --viewport /tmp/viewport.png
|
||||
```
|
||||
|
||||
### Test file upload
|
||||
|
||||
```bash
|
||||
$B goto https://app.example.com/upload
|
||||
$B snapshot -i
|
||||
$B upload @e3 /path/to/test-file.pdf
|
||||
$B is visible ".upload-success"
|
||||
$B screenshot /tmp/upload-result.png
|
||||
```
|
||||
|
||||
### Test forms with validation
|
||||
|
||||
```bash
|
||||
$B goto https://app.example.com/form
|
||||
$B snapshot -i
|
||||
|
||||
# Submit empty — check validation errors appear
|
||||
$B click @e10 # submit button
|
||||
$B snapshot -D # diff shows error messages appeared
|
||||
$B is visible ".error-message"
|
||||
|
||||
# Fill and resubmit
|
||||
$B fill @e3 "valid input"
|
||||
$B click @e10
|
||||
$B snapshot -D # diff shows errors gone, success state
|
||||
```
|
||||
|
||||
### Test dialogs (delete confirmations, prompts)
|
||||
|
||||
```bash
|
||||
# Set up dialog handling BEFORE triggering
|
||||
$B dialog-accept # will auto-accept next alert/confirm
|
||||
$B click "#delete-button" # triggers confirmation dialog
|
||||
$B dialog # see what dialog appeared
|
||||
$B snapshot -D # verify the item was deleted
|
||||
|
||||
# For prompts that need input
|
||||
$B dialog-accept "my answer" # accept with text
|
||||
$B click "#rename-button" # triggers prompt
|
||||
```
|
||||
|
||||
### Test authenticated pages (import real browser cookies)
|
||||
|
||||
```bash
|
||||
# Import cookies from your real browser (opens interactive picker)
|
||||
$B cookie-import-browser
|
||||
|
||||
# Or import a specific domain directly
|
||||
$B cookie-import-browser comet --domain .github.com
|
||||
|
||||
# Now test authenticated pages
|
||||
$B goto https://github.com/settings/profile
|
||||
$B snapshot -i
|
||||
$B screenshot /tmp/github-profile.png
|
||||
```
|
||||
|
||||
> **Cookie safety:** `cookie-import-browser` transfers real session data.
|
||||
> Only import cookies from browsers you control.
|
||||
|
||||
### Compare two pages / environments
|
||||
|
||||
```bash
|
||||
$B diff https://staging.app.com https://prod.app.com
|
||||
```
|
||||
|
||||
### Multi-step chain (efficient for long flows)
|
||||
|
||||
```bash
|
||||
echo '[
|
||||
["goto","https://app.example.com"],
|
||||
["snapshot","-i"],
|
||||
["fill","@e3","$TEST_EMAIL"],
|
||||
["fill","@e4","$TEST_PASSWORD"],
|
||||
["click","@e5"],
|
||||
["snapshot","-D"],
|
||||
["screenshot","/tmp/result.png"]
|
||||
]' | $B chain
|
||||
```
|
||||
|
||||
## Quick Assertion Patterns
|
||||
|
||||
```bash
|
||||
# Element exists and is visible
|
||||
$B is visible ".modal"
|
||||
|
||||
# Button is enabled/disabled
|
||||
$B is enabled "#submit-btn"
|
||||
$B is disabled "#submit-btn"
|
||||
|
||||
# Checkbox state
|
||||
$B is checked "#agree"
|
||||
|
||||
# Input is editable
|
||||
$B is editable "#name-field"
|
||||
|
||||
# Element has focus
|
||||
$B is focused "#search-input"
|
||||
|
||||
# Page contains text
|
||||
$B js "document.body.textContent.includes('Success')"
|
||||
|
||||
# Element count
|
||||
$B js "document.querySelectorAll('.list-item').length"
|
||||
|
||||
# Specific attribute value
|
||||
$B attrs "#logo" # returns all attributes as JSON
|
||||
|
||||
# CSS property
|
||||
$B css ".button" "background-color"
|
||||
```
|
||||
|
||||
## Snapshot System
|
||||
|
||||
{{SNAPSHOT_FLAGS}}
|
||||
|
||||
## Command Reference
|
||||
|
||||
{{COMMAND_REFERENCE}}
|
||||
|
||||
## Tips
|
||||
|
||||
1. **Navigate once, query many times.** `goto` loads the page; then `text`, `js`, `screenshot` all hit the loaded page instantly.
|
||||
2. **Use `snapshot -i` first.** See all interactive elements, then click/fill by ref. No CSS selector guessing.
|
||||
3. **Use `snapshot -D` to verify.** Baseline → action → diff. See exactly what changed.
|
||||
4. **Use `is` for assertions.** `is visible .modal` is faster and more reliable than parsing page text.
|
||||
5. **Use `snapshot -a` for evidence.** Annotated screenshots are great for bug reports.
|
||||
6. **Use `snapshot -C` for tricky UIs.** Finds clickable divs that the accessibility tree misses.
|
||||
7. **Check `console` after actions.** Catch JS errors that don't surface visually.
|
||||
8. **Use `chain` for long flows.** Single command, no per-step CLI overhead.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,105 @@
|
|||
#!/usr/bin/env bash
|
||||
# gstack-first-task-detect — classify the current project into ONE first-task
|
||||
# bucket so the first-run scaffold can suggest a concrete next skill.
|
||||
#
|
||||
# Contract (load-bearing — the preamble eval's nothing but a single token):
|
||||
# - Prints EXACTLY ONE whitelisted enum token to stdout, or nothing.
|
||||
# - Never hangs: every git call is wrapped in a portable 2s timeout.
|
||||
# - Never errors out of the caller: best-effort, fail-safe to no output.
|
||||
# - Local git + filesystem only. NO network (no gh/glab) — this runs in the
|
||||
# latency-sensitive skill preamble.
|
||||
#
|
||||
# Enum tokens (the ONLY strings this ever emits):
|
||||
# greenfield | code_node | code_python | code_rust | code_go | code_ruby
|
||||
# | code_ios | branch_ahead | dirty_default | clean_default | nongit
|
||||
#
|
||||
# The caller maps the token to human prose; no description text crosses the
|
||||
# eval boundary. Usage: TOKEN=$(gstack-first-task-detect)
|
||||
set -uo pipefail
|
||||
|
||||
# --- Portable timeout wrapper (gtimeout → timeout → unwrapped), per gstack-codex-probe ---
|
||||
_ftd_to=$(command -v gtimeout 2>/dev/null || command -v timeout 2>/dev/null || echo "")
|
||||
_git() {
|
||||
if [ -n "$_ftd_to" ]; then
|
||||
"$_ftd_to" 2 git "$@" 2>/dev/null
|
||||
else
|
||||
git "$@" 2>/dev/null
|
||||
fi
|
||||
}
|
||||
|
||||
# Emit only whitelisted tokens — defense in depth even though every emit site
|
||||
# below is a literal.
|
||||
_emit() {
|
||||
case "$1" in
|
||||
greenfield|code_node|code_python|code_rust|code_go|code_ruby|code_ios|branch_ahead|dirty_default|clean_default|nongit)
|
||||
printf '%s\n' "$1" ;;
|
||||
*) : ;; # unknown → emit nothing (caller shows no scaffold)
|
||||
esac
|
||||
exit 0
|
||||
}
|
||||
|
||||
# --- 1. Not a git repo → nothing actionable from git, but language may still help ---
|
||||
if ! _git rev-parse --is-inside-work-tree | grep -q true; then
|
||||
_emit nongit
|
||||
fi
|
||||
|
||||
# --- 2. Greenfield (no commits) ---
|
||||
_commits=$(_git rev-list --count HEAD || echo 0)
|
||||
[ -z "$_commits" ] && _commits=0
|
||||
if [ "$_commits" -eq 0 ] 2>/dev/null; then
|
||||
_emit greenfield
|
||||
fi
|
||||
|
||||
# --- 3. Resolve default + current branch (reuse the repo's base-branch fallback) ---
|
||||
_default=$(_git symbolic-ref refs/remotes/origin/HEAD | sed 's|refs/remotes/origin/||')
|
||||
if [ -z "$_default" ]; then
|
||||
if _git rev-parse --verify origin/main >/dev/null; then _default=main
|
||||
elif _git rev-parse --verify origin/master >/dev/null; then _default=master
|
||||
else _default=main
|
||||
fi
|
||||
fi
|
||||
_current=$(_git rev-parse --abbrev-ref HEAD || echo "")
|
||||
|
||||
# --- 4. On a feature branch ahead of base (local-only) → ready to review/ship ---
|
||||
if [ -n "$_current" ] && [ "$_current" != "$_default" ] && [ "$_current" != "HEAD" ]; then
|
||||
# ahead-count vs the REAL base: prefer origin/<default> (the remote truth);
|
||||
# a stale local <default> would falsely inflate the ahead count.
|
||||
_base=""
|
||||
if _git rev-parse --verify "origin/$_default" >/dev/null; then _base="origin/$_default"
|
||||
elif _git rev-parse --verify "$_default" >/dev/null; then _base="$_default"
|
||||
fi
|
||||
if [ -n "$_base" ]; then
|
||||
_ahead=$(_git rev-list --count "$_base..HEAD" || echo 0)
|
||||
[ -z "$_ahead" ] && _ahead=0
|
||||
if [ "$_ahead" -gt 0 ] 2>/dev/null; then
|
||||
_emit branch_ahead
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# --- 5. Uncommitted changes on the default branch → review + commit ---
|
||||
_dirty=$(_git status --porcelain | head -1)
|
||||
if [ -n "$_dirty" ] && { [ "$_current" = "$_default" ] || [ "$_current" = "HEAD" ] || [ -z "$_current" ]; }; then
|
||||
_emit dirty_default
|
||||
fi
|
||||
|
||||
# --- 6. Has code + a recognized language marker → verify tests/build ---
|
||||
# Resolve to the repo root first so a skill invoked from a subdir doesn't miss
|
||||
# a root-level package.json / Cargo.toml / etc. Filesystem-only after this.
|
||||
_TOP=$(_git rev-parse --show-toplevel)
|
||||
[ -n "$_TOP" ] && cd "$_TOP" 2>/dev/null || true
|
||||
# Order by specificity/likelihood; stop at first match.
|
||||
if [ -f package.json ]; then _emit code_node; fi
|
||||
if [ -f pyproject.toml ] || [ -f setup.py ] || [ -f requirements.txt ]; then _emit code_python; fi
|
||||
if [ -f Cargo.toml ]; then _emit code_rust; fi
|
||||
if [ -f go.mod ]; then _emit code_go; fi
|
||||
if [ -f Gemfile ]; then _emit code_ruby; fi
|
||||
if ls ./*.xcodeproj >/dev/null 2>&1 || [ -d ios ]; then _emit code_ios; fi
|
||||
|
||||
# --- 7. Clean default branch with history, no recognized language → pick something ---
|
||||
if [ "$_commits" -ge 5 ] 2>/dev/null; then
|
||||
_emit clean_default
|
||||
fi
|
||||
|
||||
# Nothing confidently actionable → emit nothing (no scaffold).
|
||||
exit 0
|
||||
|
|
@ -420,13 +420,54 @@ If A: run `open URL1 && open URL2 && open URL3` (opens each in default browser).
|
|||
If B/C/D: run `open` on the selected URL only.
|
||||
If E: proceed to next-skill recommendations.
|
||||
|
||||
### Next-skill recommendations
|
||||
### Next-skill recommendations — hand the user into the loop
|
||||
|
||||
After the plea, suggest the next step:
|
||||
Don't just list options. Offer to launch the next review NOW so the design doc flows
|
||||
straight into a structured review. Map the design-doc mode to the recommended option
|
||||
(default `/plan-eng-review` when ambiguous — it has the broadest real-world use and the
|
||||
strongest retention).
|
||||
|
||||
- **`/plan-ceo-review`** for ambitious features (EXPANSION mode) — rethink the problem, find the 10-star product
|
||||
- **`/plan-eng-review`** for well-scoped implementation planning — lock in architecture, tests, edge cases
|
||||
- **`/plan-design-review`** for visual/UX design review
|
||||
**If `PROACTIVE` is `false` OR `CONDUCTOR_SESSION: true`:** do NOT auto-launch. Recommend
|
||||
in one line and stop, letting the user invoke:
|
||||
- EXPANSION / ambitious → "Next: `/plan-ceo-review` to pressure-test scope and find the 10-star product."
|
||||
- well-scoped → "Next: `/plan-eng-review` to lock architecture, tests, and edge cases."
|
||||
- visual/UX-heavy → "Next: `/plan-design-review` for a visual/UX pass."
|
||||
|
||||
**Otherwise**, offer via AskUserQuestion (D<N> format from the preamble):
|
||||
|
||||
D<N> — Run the next review now?
|
||||
Project/branch/task: the design doc you just wrote for this feature.
|
||||
ELI10: You just wrote a design doc. The natural next step is a structured review that
|
||||
catches scope and architecture problems before you build. I can launch it right now, or
|
||||
you can run it later yourself.
|
||||
Stakes if we pick wrong: skipping review means problems surface mid-build, costing rework.
|
||||
Recommendation: the mode-mapped option (`/plan-eng-review` if unsure) because it locks the
|
||||
plan before any code is written.
|
||||
Completeness: A=10/10, B=9/10, C=8/10, D=3/10
|
||||
Pros / cons:
|
||||
A) Run /plan-eng-review now (recommended)
|
||||
✅ Locks architecture, tests, and edge cases before a line of code is written
|
||||
❌ Adds ~15 min CC now (human: 1-2 hrs of review compressed)
|
||||
B) Run /plan-ceo-review now
|
||||
✅ Pressure-tests ambition and scope — finds the 10-star version of the product
|
||||
❌ Lower value when the scope is already tight and well understood
|
||||
C) Run /plan-design-review now
|
||||
✅ Catches visual/UX problems while they are still cheap plan-stage changes
|
||||
❌ Little value for backend-only or non-visual features
|
||||
D) Not now — I'll run a review later
|
||||
✅ Keeps you in flow if you want to start building immediately
|
||||
❌ Review gaps compound; problems get more expensive after code exists
|
||||
Net: 15 minutes of structured review now against rework risk later.
|
||||
|
||||
On the user's SELECTION of A/B/C (not on invocation success), log the handoff, then invoke
|
||||
the chosen skill via the **Skill tool** (it auto-discovers the design doc):
|
||||
```bash
|
||||
~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type handoff --skill office-hours --outcome accepted --session-id "$_SESSION_ID" 2>/dev/null || true
|
||||
```
|
||||
On D, log declined and stop:
|
||||
```bash
|
||||
~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type handoff --skill office-hours --outcome declined --session-id "$_SESSION_ID" 2>/dev/null || true
|
||||
```
|
||||
|
||||
The design doc at `~/.gstack/projects/` is automatically discoverable by downstream skills — they will read it during their pre-review system audit.
|
||||
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ import {
|
|||
import { generateLakeIntro } from './preamble/generate-lake-intro';
|
||||
import { generateTelemetryPrompt } from './preamble/generate-telemetry-prompt';
|
||||
import { generateProactivePrompt } from './preamble/generate-proactive-prompt';
|
||||
import { generateFirstRunGuidance } from './preamble/generate-first-run-guidance';
|
||||
import { generateRoutingInjection } from './preamble/generate-routing-injection';
|
||||
import { generateVendoringDeprecation } from './preamble/generate-vendoring-deprecation';
|
||||
import { generateSpawnedSessionCheck } from './preamble/generate-spawned-session-check';
|
||||
|
|
@ -94,6 +95,7 @@ export function generatePreamble(ctx: TemplateContext): string {
|
|||
generateLakeIntro(),
|
||||
generateTelemetryPrompt(ctx),
|
||||
generateProactivePrompt(ctx),
|
||||
generateFirstRunGuidance(ctx),
|
||||
generateRoutingInjection(ctx),
|
||||
generateVendoringDeprecation(ctx),
|
||||
generateSpawnedSessionCheck(),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
import type { TemplateContext } from '../types';
|
||||
|
||||
// First-run guidance (P4 scaffold + P3 loop tip), unified into one section.
|
||||
// Branches on the persistent `.activated` lifecycle marker — NOT `_SESSIONS`,
|
||||
// which counts concurrent sessions in the last 120 min, not first-vs-returning.
|
||||
//
|
||||
// The FIRST_TASK enum is computed at runtime in generate-preamble-bash.ts (gated
|
||||
// so the detector only runs on the first run) and printed as `FIRST_TASK: <token>`.
|
||||
// This section maps the token the model SAW in that output to a one-line nudge
|
||||
// (no description string ever crosses an eval boundary) and sets markers:
|
||||
// ~/.gstack/.activated — set at the end of the first-ever skill run
|
||||
// ~/.gstack/.first-loop-tip-shown — set when the returning-session tip is shown
|
||||
//
|
||||
// Note: bash blocks run in separate shells, so the runtime token cannot be read
|
||||
// from a shell var here — the model substitutes the token it saw for TASK_TOKEN,
|
||||
// exactly like the Telemetry section substitutes SKILL_NAME/OUTCOME.
|
||||
export function generateFirstRunGuidance(ctx: TemplateContext): string {
|
||||
return `## First-run guidance (one-time)
|
||||
|
||||
If \`ACTIVATED\` is \`no\` (first skill run on this machine) AND the preamble printed a non-empty \`FIRST_TASK:\` value that is NOT \`nongit\`: show ONE short, project-specific line mapped from the token, as a heads-up, then CONTINUE with whatever the user actually asked — do NOT halt their task. Map the token: \`greenfield\` → "Fresh repo — shape it first with \`/spec\` or \`/office-hours\`." \`code_node\`/\`code_python\`/\`code_rust\`/\`code_go\`/\`code_ruby\`/\`code_ios\` → "There's code here — \`/qa\` to see it work, or \`/investigate\` if something's off." \`branch_ahead\` → "Unshipped work on this branch — \`/review\` then \`/ship\`." \`dirty_default\` → "Uncommitted changes — \`/review\` before committing." \`clean_default\` → "Pick one: \`/spec\`, \`/investigate\`, or \`/qa\`." Then substitute the token you saw for TASK_TOKEN and run (best-effort), and mark activated:
|
||||
\`\`\`bash
|
||||
${ctx.paths.binDir}/gstack-telemetry-log --event-type first_task_scaffold_shown --skill "TASK_TOKEN" --outcome shown 2>/dev/null || true
|
||||
touch ~/.gstack/.activated 2>/dev/null || true
|
||||
\`\`\`
|
||||
|
||||
If \`ACTIVATED\` is \`no\` but \`FIRST_TASK:\` is empty or \`nongit\` (headless, non-git, or nothing actionable): show nothing, just run \`touch ~/.gstack/.activated 2>/dev/null || true\`.
|
||||
|
||||
Else if \`ACTIVATED\` is \`yes\` AND \`FIRST_LOOP_SHOWN\` is \`no\`: say once as a heads-up (then continue):
|
||||
|
||||
> Tip: gstack pays off when you complete one loop — **plan → review → ship**. A common first loop: \`/office-hours\` or \`/spec\` to shape it, \`/plan-eng-review\` to lock it, then \`/ship\`.
|
||||
|
||||
Then run \`touch ~/.gstack/.first-loop-tip-shown 2>/dev/null || true\`.
|
||||
|
||||
Skip this section if \`ACTIVATED\` and \`FIRST_LOOP_SHOWN\` are both \`yes\`.`;
|
||||
}
|
||||
|
|
@ -43,6 +43,17 @@ echo "SESSION_KIND: $_SESSION_KIND"
|
|||
if [ "$_SESSION_KIND" != "headless" ] && { [ -n "\${CONDUCTOR_WORKSPACE_PATH:-}" ] || [ -n "\${CONDUCTOR_PORT:-}" ]; }; then
|
||||
echo "CONDUCTOR_SESSION: true"
|
||||
fi
|
||||
_ACTIVATED=$([ -f ~/.gstack/.activated ] && echo "yes" || echo "no")
|
||||
_FIRST_LOOP_SHOWN=$([ -f ~/.gstack/.first-loop-tip-shown ] && echo "yes" || echo "no")
|
||||
echo "ACTIVATED: $_ACTIVATED"
|
||||
echo "FIRST_LOOP_SHOWN: $_FIRST_LOOP_SHOWN"
|
||||
# First-run project detection: run the detector ONLY on the first-ever skill run
|
||||
# (ACTIVATED=no, interactive) so it stays off the hot path for every run after.
|
||||
_FIRST_TASK=""
|
||||
if [ "$_ACTIVATED" = "no" ] && [ "$_SESSION_KIND" != "headless" ]; then
|
||||
_FIRST_TASK=$(${ctx.paths.binDir}/gstack-first-task-detect 2>/dev/null || true)
|
||||
fi
|
||||
echo "FIRST_TASK: $_FIRST_TASK"
|
||||
_LAKE_SEEN=$([ -f ~/.gstack/.completeness-intro-seen ] && echo "yes" || echo "no")
|
||||
echo "LAKE_INTRO: $_LAKE_SEEN"
|
||||
_TEL=$(${ctx.paths.binDir}/gstack-config get telemetry 2>/dev/null || true)
|
||||
|
|
|
|||
11
setup
11
setup
|
|
@ -1229,7 +1229,16 @@ fi
|
|||
|
||||
# 9. First-time welcome + legacy cleanup
|
||||
if [ ! -f "$HOME/.gstack/.welcome-seen" ]; then
|
||||
log " Welcome! Run /gstack-upgrade anytime to stay current."
|
||||
log ""
|
||||
log " gstack is ready. First move:"
|
||||
log " New idea / empty repo? /office-hours or /spec"
|
||||
log " Existing code? /qa to see it work, or /investigate"
|
||||
log " (Run /gstack-upgrade anytime to stay current)"
|
||||
log ""
|
||||
# Best-effort onboarding telemetry (respects telemetry!=off; never blocks setup).
|
||||
if [ -x "$SOURCE_GSTACK_DIR/bin/gstack-telemetry-log" ]; then
|
||||
"$SOURCE_GSTACK_DIR/bin/gstack-telemetry-log" --event-type onboarding --skill _setup_welcome --outcome shown >/dev/null 2>&1 || true
|
||||
fi
|
||||
touch "$HOME/.gstack/.welcome-seen"
|
||||
fi
|
||||
rm -f /tmp/gstack-latest-version
|
||||
|
|
|
|||
|
|
@ -67,8 +67,18 @@ Deno.serve(async (req) => {
|
|||
// Validate schema version
|
||||
if (event.v !== 1) continue;
|
||||
|
||||
// Validate event_type
|
||||
const validTypes = ["skill_run", "upgrade_prompted", "upgrade_completed"];
|
||||
// Validate event_type. Activation-funnel events (v1.x) join the originals:
|
||||
// onboarding (P0 setup nudge), first_task_scaffold_shown (P4 first-run
|
||||
// scaffold), handoff (P1 office-hours → next skill), route (gstack router).
|
||||
const validTypes = [
|
||||
"skill_run",
|
||||
"upgrade_prompted",
|
||||
"upgrade_completed",
|
||||
"onboarding",
|
||||
"first_task_scaffold_shown",
|
||||
"handoff",
|
||||
"route",
|
||||
];
|
||||
if (!validTypes.includes(event.event_type)) continue;
|
||||
|
||||
rows.push({
|
||||
|
|
|
|||
Loading…
Reference in New Issue