mirror of
https://github.com/garrytan/gstack.git
synced 2026-05-05 21:25:27 +02:00
chore: merge main into feature branch
Resolved conflicts: - VERSION: keep 0.9.0 (ours supersedes 0.8.6) - CHANGELOG: prepend 0.9.0 above new 0.8.6 entry - gen-skill-docs.ts: integrate telemetry (pending finalize, prompt) into decomposed sub-functions with host-aware paths - gen-skill-docs.test.ts: keep both Codex generation and telemetry tests Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -27,8 +27,15 @@ echo "BRANCH: $_BRANCH"
|
||||
echo "PROACTIVE: $_PROACTIVE"
|
||||
_LAKE_SEEN=$([ -f ~/.gstack/.completeness-intro-seen ] && echo "yes" || echo "no")
|
||||
echo "LAKE_INTRO: $_LAKE_SEEN"
|
||||
_TEL=$(~/.codex/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"
|
||||
mkdir -p ~/.gstack/analytics
|
||||
echo '{"skill":"browse","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
|
||||
for _PF in ~/.gstack/analytics/.pending-*; do [ -f "$_PF" ] && ~/.codex/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown --session-id "$_SESSION_ID" 2>/dev/null || true; break; done
|
||||
```
|
||||
|
||||
If `PROACTIVE` is `"false"`, do not proactively suggest gstack skills — only invoke
|
||||
@@ -48,6 +55,27 @@ touch ~/.gstack/.completeness-intro-seen
|
||||
|
||||
Only run `open` if the user says yes. Always run `touch` to mark as seen. This only happens once.
|
||||
|
||||
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: After the lake intro is handled,
|
||||
ask the user about telemetry. Use AskUserQuestion:
|
||||
|
||||
> gstack can share anonymous usage data (which skills you use, how long they take, crash info)
|
||||
> to help improve the project. No code, file paths, or repo names are ever sent.
|
||||
> Change anytime with `gstack-config set telemetry off`.
|
||||
|
||||
Options:
|
||||
- A) Yes, share anonymous data (recommended)
|
||||
- B) No thanks
|
||||
|
||||
If A: run `~/.codex/skills/gstack/bin/gstack-config set telemetry anonymous`
|
||||
If B: run `~/.codex/skills/gstack/bin/gstack-config set telemetry off`
|
||||
|
||||
Always run:
|
||||
```bash
|
||||
touch ~/.gstack/.telemetry-prompted
|
||||
```
|
||||
|
||||
This only happens once. If `TEL_PROMPTED` is `yes`, skip this entirely.
|
||||
|
||||
## AskUserQuestion Format
|
||||
|
||||
**ALWAYS follow this structure for every AskUserQuestion call:**
|
||||
@@ -147,6 +175,27 @@ ATTEMPTED: [what you tried]
|
||||
RECOMMENDATION: [what the user should do next]
|
||||
```
|
||||
|
||||
## Telemetry (run last)
|
||||
|
||||
After the skill workflow completes (success, error, or abort), log the telemetry event.
|
||||
Determine the skill name from the `name:` field in this file's YAML frontmatter.
|
||||
Determine the outcome from the workflow result (success if completed normally, error
|
||||
if it failed, abort if the user interrupted). Run this bash:
|
||||
|
||||
```bash
|
||||
_TEL_END=$(date +%s)
|
||||
_TEL_DUR=$(( _TEL_END - _TEL_START ))
|
||||
rm -f ~/.gstack/analytics/.pending-"$_SESSION_ID" 2>/dev/null || true
|
||||
~/.codex/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 &
|
||||
```
|
||||
|
||||
Replace `SKILL_NAME` with the actual skill name from frontmatter, `OUTCOME` with
|
||||
success/error/abort, and `USED_BROWSE` with true/false based on whether `$B` was used.
|
||||
If you cannot determine the outcome, use "unknown". This runs in the background and
|
||||
never blocks the user.
|
||||
|
||||
# browse: QA Testing & Dogfooding
|
||||
|
||||
Persistent headless Chromium. First call auto-starts (~3s), then ~100ms per command.
|
||||
|
||||
@@ -28,8 +28,15 @@ echo "BRANCH: $_BRANCH"
|
||||
echo "PROACTIVE: $_PROACTIVE"
|
||||
_LAKE_SEEN=$([ -f ~/.gstack/.completeness-intro-seen ] && echo "yes" || echo "no")
|
||||
echo "LAKE_INTRO: $_LAKE_SEEN"
|
||||
_TEL=$(~/.codex/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"
|
||||
mkdir -p ~/.gstack/analytics
|
||||
echo '{"skill":"design-consultation","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
|
||||
for _PF in ~/.gstack/analytics/.pending-*; do [ -f "$_PF" ] && ~/.codex/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown --session-id "$_SESSION_ID" 2>/dev/null || true; break; done
|
||||
```
|
||||
|
||||
If `PROACTIVE` is `"false"`, do not proactively suggest gstack skills — only invoke
|
||||
@@ -49,6 +56,27 @@ touch ~/.gstack/.completeness-intro-seen
|
||||
|
||||
Only run `open` if the user says yes. Always run `touch` to mark as seen. This only happens once.
|
||||
|
||||
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: After the lake intro is handled,
|
||||
ask the user about telemetry. Use AskUserQuestion:
|
||||
|
||||
> gstack can share anonymous usage data (which skills you use, how long they take, crash info)
|
||||
> to help improve the project. No code, file paths, or repo names are ever sent.
|
||||
> Change anytime with `gstack-config set telemetry off`.
|
||||
|
||||
Options:
|
||||
- A) Yes, share anonymous data (recommended)
|
||||
- B) No thanks
|
||||
|
||||
If A: run `~/.codex/skills/gstack/bin/gstack-config set telemetry anonymous`
|
||||
If B: run `~/.codex/skills/gstack/bin/gstack-config set telemetry off`
|
||||
|
||||
Always run:
|
||||
```bash
|
||||
touch ~/.gstack/.telemetry-prompted
|
||||
```
|
||||
|
||||
This only happens once. If `TEL_PROMPTED` is `yes`, skip this entirely.
|
||||
|
||||
## AskUserQuestion Format
|
||||
|
||||
**ALWAYS follow this structure for every AskUserQuestion call:**
|
||||
@@ -148,6 +176,27 @@ ATTEMPTED: [what you tried]
|
||||
RECOMMENDATION: [what the user should do next]
|
||||
```
|
||||
|
||||
## Telemetry (run last)
|
||||
|
||||
After the skill workflow completes (success, error, or abort), log the telemetry event.
|
||||
Determine the skill name from the `name:` field in this file's YAML frontmatter.
|
||||
Determine the outcome from the workflow result (success if completed normally, error
|
||||
if it failed, abort if the user interrupted). Run this bash:
|
||||
|
||||
```bash
|
||||
_TEL_END=$(date +%s)
|
||||
_TEL_DUR=$(( _TEL_END - _TEL_START ))
|
||||
rm -f ~/.gstack/analytics/.pending-"$_SESSION_ID" 2>/dev/null || true
|
||||
~/.codex/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 &
|
||||
```
|
||||
|
||||
Replace `SKILL_NAME` with the actual skill name from frontmatter, `OUTCOME` with
|
||||
success/error/abort, and `USED_BROWSE` with true/false based on whether `$B` was used.
|
||||
If you cannot determine the outcome, use "unknown". This runs in the background and
|
||||
never blocks the user.
|
||||
|
||||
# /design-consultation: Your Design System, Built Together
|
||||
|
||||
You are a senior product designer with strong opinions about typography, color, and visual systems. You don't present menus — you listen, think, research, and propose. You're opinionated but not dogmatic. You explain your reasoning and welcome pushback.
|
||||
|
||||
@@ -28,8 +28,15 @@ echo "BRANCH: $_BRANCH"
|
||||
echo "PROACTIVE: $_PROACTIVE"
|
||||
_LAKE_SEEN=$([ -f ~/.gstack/.completeness-intro-seen ] && echo "yes" || echo "no")
|
||||
echo "LAKE_INTRO: $_LAKE_SEEN"
|
||||
_TEL=$(~/.codex/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"
|
||||
mkdir -p ~/.gstack/analytics
|
||||
echo '{"skill":"design-review","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
|
||||
for _PF in ~/.gstack/analytics/.pending-*; do [ -f "$_PF" ] && ~/.codex/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown --session-id "$_SESSION_ID" 2>/dev/null || true; break; done
|
||||
```
|
||||
|
||||
If `PROACTIVE` is `"false"`, do not proactively suggest gstack skills — only invoke
|
||||
@@ -49,6 +56,27 @@ touch ~/.gstack/.completeness-intro-seen
|
||||
|
||||
Only run `open` if the user says yes. Always run `touch` to mark as seen. This only happens once.
|
||||
|
||||
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: After the lake intro is handled,
|
||||
ask the user about telemetry. Use AskUserQuestion:
|
||||
|
||||
> gstack can share anonymous usage data (which skills you use, how long they take, crash info)
|
||||
> to help improve the project. No code, file paths, or repo names are ever sent.
|
||||
> Change anytime with `gstack-config set telemetry off`.
|
||||
|
||||
Options:
|
||||
- A) Yes, share anonymous data (recommended)
|
||||
- B) No thanks
|
||||
|
||||
If A: run `~/.codex/skills/gstack/bin/gstack-config set telemetry anonymous`
|
||||
If B: run `~/.codex/skills/gstack/bin/gstack-config set telemetry off`
|
||||
|
||||
Always run:
|
||||
```bash
|
||||
touch ~/.gstack/.telemetry-prompted
|
||||
```
|
||||
|
||||
This only happens once. If `TEL_PROMPTED` is `yes`, skip this entirely.
|
||||
|
||||
## AskUserQuestion Format
|
||||
|
||||
**ALWAYS follow this structure for every AskUserQuestion call:**
|
||||
@@ -148,6 +176,27 @@ ATTEMPTED: [what you tried]
|
||||
RECOMMENDATION: [what the user should do next]
|
||||
```
|
||||
|
||||
## Telemetry (run last)
|
||||
|
||||
After the skill workflow completes (success, error, or abort), log the telemetry event.
|
||||
Determine the skill name from the `name:` field in this file's YAML frontmatter.
|
||||
Determine the outcome from the workflow result (success if completed normally, error
|
||||
if it failed, abort if the user interrupted). Run this bash:
|
||||
|
||||
```bash
|
||||
_TEL_END=$(date +%s)
|
||||
_TEL_DUR=$(( _TEL_END - _TEL_START ))
|
||||
rm -f ~/.gstack/analytics/.pending-"$_SESSION_ID" 2>/dev/null || true
|
||||
~/.codex/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 &
|
||||
```
|
||||
|
||||
Replace `SKILL_NAME` with the actual skill name from frontmatter, `OUTCOME` with
|
||||
success/error/abort, and `USED_BROWSE` with true/false based on whether `$B` was used.
|
||||
If you cannot determine the outcome, use "unknown". This runs in the background and
|
||||
never blocks the user.
|
||||
|
||||
# /design-review: Design Audit → Fix → Verify
|
||||
|
||||
You are a senior product designer AND a frontend engineer. Review live sites with exacting visual standards — then fix what you find. You have strong opinions about typography, spacing, and visual hierarchy, and zero tolerance for generic or AI-generated-looking interfaces.
|
||||
|
||||
@@ -26,8 +26,15 @@ echo "BRANCH: $_BRANCH"
|
||||
echo "PROACTIVE: $_PROACTIVE"
|
||||
_LAKE_SEEN=$([ -f ~/.gstack/.completeness-intro-seen ] && echo "yes" || echo "no")
|
||||
echo "LAKE_INTRO: $_LAKE_SEEN"
|
||||
_TEL=$(~/.codex/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"
|
||||
mkdir -p ~/.gstack/analytics
|
||||
echo '{"skill":"document-release","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
|
||||
for _PF in ~/.gstack/analytics/.pending-*; do [ -f "$_PF" ] && ~/.codex/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown --session-id "$_SESSION_ID" 2>/dev/null || true; break; done
|
||||
```
|
||||
|
||||
If `PROACTIVE` is `"false"`, do not proactively suggest gstack skills — only invoke
|
||||
@@ -47,6 +54,27 @@ touch ~/.gstack/.completeness-intro-seen
|
||||
|
||||
Only run `open` if the user says yes. Always run `touch` to mark as seen. This only happens once.
|
||||
|
||||
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: After the lake intro is handled,
|
||||
ask the user about telemetry. Use AskUserQuestion:
|
||||
|
||||
> gstack can share anonymous usage data (which skills you use, how long they take, crash info)
|
||||
> to help improve the project. No code, file paths, or repo names are ever sent.
|
||||
> Change anytime with `gstack-config set telemetry off`.
|
||||
|
||||
Options:
|
||||
- A) Yes, share anonymous data (recommended)
|
||||
- B) No thanks
|
||||
|
||||
If A: run `~/.codex/skills/gstack/bin/gstack-config set telemetry anonymous`
|
||||
If B: run `~/.codex/skills/gstack/bin/gstack-config set telemetry off`
|
||||
|
||||
Always run:
|
||||
```bash
|
||||
touch ~/.gstack/.telemetry-prompted
|
||||
```
|
||||
|
||||
This only happens once. If `TEL_PROMPTED` is `yes`, skip this entirely.
|
||||
|
||||
## AskUserQuestion Format
|
||||
|
||||
**ALWAYS follow this structure for every AskUserQuestion call:**
|
||||
@@ -146,6 +174,27 @@ ATTEMPTED: [what you tried]
|
||||
RECOMMENDATION: [what the user should do next]
|
||||
```
|
||||
|
||||
## Telemetry (run last)
|
||||
|
||||
After the skill workflow completes (success, error, or abort), log the telemetry event.
|
||||
Determine the skill name from the `name:` field in this file's YAML frontmatter.
|
||||
Determine the outcome from the workflow result (success if completed normally, error
|
||||
if it failed, abort if the user interrupted). Run this bash:
|
||||
|
||||
```bash
|
||||
_TEL_END=$(date +%s)
|
||||
_TEL_DUR=$(( _TEL_END - _TEL_START ))
|
||||
rm -f ~/.gstack/analytics/.pending-"$_SESSION_ID" 2>/dev/null || true
|
||||
~/.codex/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 &
|
||||
```
|
||||
|
||||
Replace `SKILL_NAME` with the actual skill name from frontmatter, `OUTCOME` with
|
||||
success/error/abort, and `USED_BROWSE` with true/false based on whether `$B` was used.
|
||||
If you cannot determine the outcome, use "unknown". This runs in the background and
|
||||
never blocks the user.
|
||||
|
||||
## Step 0: Detect base branch
|
||||
|
||||
Determine which branch this PR targets. Use the result as "the base branch" in all subsequent steps.
|
||||
|
||||
@@ -29,8 +29,15 @@ echo "BRANCH: $_BRANCH"
|
||||
echo "PROACTIVE: $_PROACTIVE"
|
||||
_LAKE_SEEN=$([ -f ~/.gstack/.completeness-intro-seen ] && echo "yes" || echo "no")
|
||||
echo "LAKE_INTRO: $_LAKE_SEEN"
|
||||
_TEL=$(~/.codex/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"
|
||||
mkdir -p ~/.gstack/analytics
|
||||
echo '{"skill":"investigate","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
|
||||
for _PF in ~/.gstack/analytics/.pending-*; do [ -f "$_PF" ] && ~/.codex/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown --session-id "$_SESSION_ID" 2>/dev/null || true; break; done
|
||||
```
|
||||
|
||||
If `PROACTIVE` is `"false"`, do not proactively suggest gstack skills — only invoke
|
||||
@@ -50,6 +57,27 @@ touch ~/.gstack/.completeness-intro-seen
|
||||
|
||||
Only run `open` if the user says yes. Always run `touch` to mark as seen. This only happens once.
|
||||
|
||||
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: After the lake intro is handled,
|
||||
ask the user about telemetry. Use AskUserQuestion:
|
||||
|
||||
> gstack can share anonymous usage data (which skills you use, how long they take, crash info)
|
||||
> to help improve the project. No code, file paths, or repo names are ever sent.
|
||||
> Change anytime with `gstack-config set telemetry off`.
|
||||
|
||||
Options:
|
||||
- A) Yes, share anonymous data (recommended)
|
||||
- B) No thanks
|
||||
|
||||
If A: run `~/.codex/skills/gstack/bin/gstack-config set telemetry anonymous`
|
||||
If B: run `~/.codex/skills/gstack/bin/gstack-config set telemetry off`
|
||||
|
||||
Always run:
|
||||
```bash
|
||||
touch ~/.gstack/.telemetry-prompted
|
||||
```
|
||||
|
||||
This only happens once. If `TEL_PROMPTED` is `yes`, skip this entirely.
|
||||
|
||||
## AskUserQuestion Format
|
||||
|
||||
**ALWAYS follow this structure for every AskUserQuestion call:**
|
||||
@@ -149,6 +177,27 @@ ATTEMPTED: [what you tried]
|
||||
RECOMMENDATION: [what the user should do next]
|
||||
```
|
||||
|
||||
## Telemetry (run last)
|
||||
|
||||
After the skill workflow completes (success, error, or abort), log the telemetry event.
|
||||
Determine the skill name from the `name:` field in this file's YAML frontmatter.
|
||||
Determine the outcome from the workflow result (success if completed normally, error
|
||||
if it failed, abort if the user interrupted). Run this bash:
|
||||
|
||||
```bash
|
||||
_TEL_END=$(date +%s)
|
||||
_TEL_DUR=$(( _TEL_END - _TEL_START ))
|
||||
rm -f ~/.gstack/analytics/.pending-"$_SESSION_ID" 2>/dev/null || true
|
||||
~/.codex/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 &
|
||||
```
|
||||
|
||||
Replace `SKILL_NAME` with the actual skill name from frontmatter, `OUTCOME` with
|
||||
success/error/abort, and `USED_BROWSE` with true/false based on whether `$B` was used.
|
||||
If you cannot determine the outcome, use "unknown". This runs in the background and
|
||||
never blocks the user.
|
||||
|
||||
# Systematic Debugging
|
||||
|
||||
## Iron Law
|
||||
|
||||
@@ -30,8 +30,15 @@ echo "BRANCH: $_BRANCH"
|
||||
echo "PROACTIVE: $_PROACTIVE"
|
||||
_LAKE_SEEN=$([ -f ~/.gstack/.completeness-intro-seen ] && echo "yes" || echo "no")
|
||||
echo "LAKE_INTRO: $_LAKE_SEEN"
|
||||
_TEL=$(~/.codex/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"
|
||||
mkdir -p ~/.gstack/analytics
|
||||
echo '{"skill":"office-hours","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
|
||||
for _PF in ~/.gstack/analytics/.pending-*; do [ -f "$_PF" ] && ~/.codex/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown --session-id "$_SESSION_ID" 2>/dev/null || true; break; done
|
||||
```
|
||||
|
||||
If `PROACTIVE` is `"false"`, do not proactively suggest gstack skills — only invoke
|
||||
@@ -51,6 +58,27 @@ touch ~/.gstack/.completeness-intro-seen
|
||||
|
||||
Only run `open` if the user says yes. Always run `touch` to mark as seen. This only happens once.
|
||||
|
||||
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: After the lake intro is handled,
|
||||
ask the user about telemetry. Use AskUserQuestion:
|
||||
|
||||
> gstack can share anonymous usage data (which skills you use, how long they take, crash info)
|
||||
> to help improve the project. No code, file paths, or repo names are ever sent.
|
||||
> Change anytime with `gstack-config set telemetry off`.
|
||||
|
||||
Options:
|
||||
- A) Yes, share anonymous data (recommended)
|
||||
- B) No thanks
|
||||
|
||||
If A: run `~/.codex/skills/gstack/bin/gstack-config set telemetry anonymous`
|
||||
If B: run `~/.codex/skills/gstack/bin/gstack-config set telemetry off`
|
||||
|
||||
Always run:
|
||||
```bash
|
||||
touch ~/.gstack/.telemetry-prompted
|
||||
```
|
||||
|
||||
This only happens once. If `TEL_PROMPTED` is `yes`, skip this entirely.
|
||||
|
||||
## AskUserQuestion Format
|
||||
|
||||
**ALWAYS follow this structure for every AskUserQuestion call:**
|
||||
@@ -150,6 +178,27 @@ ATTEMPTED: [what you tried]
|
||||
RECOMMENDATION: [what the user should do next]
|
||||
```
|
||||
|
||||
## Telemetry (run last)
|
||||
|
||||
After the skill workflow completes (success, error, or abort), log the telemetry event.
|
||||
Determine the skill name from the `name:` field in this file's YAML frontmatter.
|
||||
Determine the outcome from the workflow result (success if completed normally, error
|
||||
if it failed, abort if the user interrupted). Run this bash:
|
||||
|
||||
```bash
|
||||
_TEL_END=$(date +%s)
|
||||
_TEL_DUR=$(( _TEL_END - _TEL_START ))
|
||||
rm -f ~/.gstack/analytics/.pending-"$_SESSION_ID" 2>/dev/null || true
|
||||
~/.codex/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 &
|
||||
```
|
||||
|
||||
Replace `SKILL_NAME` with the actual skill name from frontmatter, `OUTCOME` with
|
||||
success/error/abort, and `USED_BROWSE` with true/false based on whether `$B` was used.
|
||||
If you cannot determine the outcome, use "unknown". This runs in the background and
|
||||
never blocks the user.
|
||||
|
||||
# YC Office Hours
|
||||
|
||||
You are a **YC office hours partner**. Your job is to ensure the problem is understood before solutions are proposed. You adapt to what the user is building — startup founders get the hard questions, builders get an enthusiastic collaborator. This skill produces design docs, not code.
|
||||
|
||||
@@ -29,8 +29,15 @@ echo "BRANCH: $_BRANCH"
|
||||
echo "PROACTIVE: $_PROACTIVE"
|
||||
_LAKE_SEEN=$([ -f ~/.gstack/.completeness-intro-seen ] && echo "yes" || echo "no")
|
||||
echo "LAKE_INTRO: $_LAKE_SEEN"
|
||||
_TEL=$(~/.codex/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"
|
||||
mkdir -p ~/.gstack/analytics
|
||||
echo '{"skill":"plan-ceo-review","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
|
||||
for _PF in ~/.gstack/analytics/.pending-*; do [ -f "$_PF" ] && ~/.codex/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown --session-id "$_SESSION_ID" 2>/dev/null || true; break; done
|
||||
```
|
||||
|
||||
If `PROACTIVE` is `"false"`, do not proactively suggest gstack skills — only invoke
|
||||
@@ -50,6 +57,27 @@ touch ~/.gstack/.completeness-intro-seen
|
||||
|
||||
Only run `open` if the user says yes. Always run `touch` to mark as seen. This only happens once.
|
||||
|
||||
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: After the lake intro is handled,
|
||||
ask the user about telemetry. Use AskUserQuestion:
|
||||
|
||||
> gstack can share anonymous usage data (which skills you use, how long they take, crash info)
|
||||
> to help improve the project. No code, file paths, or repo names are ever sent.
|
||||
> Change anytime with `gstack-config set telemetry off`.
|
||||
|
||||
Options:
|
||||
- A) Yes, share anonymous data (recommended)
|
||||
- B) No thanks
|
||||
|
||||
If A: run `~/.codex/skills/gstack/bin/gstack-config set telemetry anonymous`
|
||||
If B: run `~/.codex/skills/gstack/bin/gstack-config set telemetry off`
|
||||
|
||||
Always run:
|
||||
```bash
|
||||
touch ~/.gstack/.telemetry-prompted
|
||||
```
|
||||
|
||||
This only happens once. If `TEL_PROMPTED` is `yes`, skip this entirely.
|
||||
|
||||
## AskUserQuestion Format
|
||||
|
||||
**ALWAYS follow this structure for every AskUserQuestion call:**
|
||||
@@ -149,6 +177,27 @@ ATTEMPTED: [what you tried]
|
||||
RECOMMENDATION: [what the user should do next]
|
||||
```
|
||||
|
||||
## Telemetry (run last)
|
||||
|
||||
After the skill workflow completes (success, error, or abort), log the telemetry event.
|
||||
Determine the skill name from the `name:` field in this file's YAML frontmatter.
|
||||
Determine the outcome from the workflow result (success if completed normally, error
|
||||
if it failed, abort if the user interrupted). Run this bash:
|
||||
|
||||
```bash
|
||||
_TEL_END=$(date +%s)
|
||||
_TEL_DUR=$(( _TEL_END - _TEL_START ))
|
||||
rm -f ~/.gstack/analytics/.pending-"$_SESSION_ID" 2>/dev/null || true
|
||||
~/.codex/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 &
|
||||
```
|
||||
|
||||
Replace `SKILL_NAME` with the actual skill name from frontmatter, `OUTCOME` with
|
||||
success/error/abort, and `USED_BROWSE` with true/false based on whether `$B` was used.
|
||||
If you cannot determine the outcome, use "unknown". This runs in the background and
|
||||
never blocks the user.
|
||||
|
||||
## Step 0: Detect base branch
|
||||
|
||||
Determine which branch this PR targets. Use the result as "the base branch" in all subsequent steps.
|
||||
|
||||
@@ -28,8 +28,15 @@ echo "BRANCH: $_BRANCH"
|
||||
echo "PROACTIVE: $_PROACTIVE"
|
||||
_LAKE_SEEN=$([ -f ~/.gstack/.completeness-intro-seen ] && echo "yes" || echo "no")
|
||||
echo "LAKE_INTRO: $_LAKE_SEEN"
|
||||
_TEL=$(~/.codex/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"
|
||||
mkdir -p ~/.gstack/analytics
|
||||
echo '{"skill":"plan-design-review","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
|
||||
for _PF in ~/.gstack/analytics/.pending-*; do [ -f "$_PF" ] && ~/.codex/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown --session-id "$_SESSION_ID" 2>/dev/null || true; break; done
|
||||
```
|
||||
|
||||
If `PROACTIVE` is `"false"`, do not proactively suggest gstack skills — only invoke
|
||||
@@ -49,6 +56,27 @@ touch ~/.gstack/.completeness-intro-seen
|
||||
|
||||
Only run `open` if the user says yes. Always run `touch` to mark as seen. This only happens once.
|
||||
|
||||
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: After the lake intro is handled,
|
||||
ask the user about telemetry. Use AskUserQuestion:
|
||||
|
||||
> gstack can share anonymous usage data (which skills you use, how long they take, crash info)
|
||||
> to help improve the project. No code, file paths, or repo names are ever sent.
|
||||
> Change anytime with `gstack-config set telemetry off`.
|
||||
|
||||
Options:
|
||||
- A) Yes, share anonymous data (recommended)
|
||||
- B) No thanks
|
||||
|
||||
If A: run `~/.codex/skills/gstack/bin/gstack-config set telemetry anonymous`
|
||||
If B: run `~/.codex/skills/gstack/bin/gstack-config set telemetry off`
|
||||
|
||||
Always run:
|
||||
```bash
|
||||
touch ~/.gstack/.telemetry-prompted
|
||||
```
|
||||
|
||||
This only happens once. If `TEL_PROMPTED` is `yes`, skip this entirely.
|
||||
|
||||
## AskUserQuestion Format
|
||||
|
||||
**ALWAYS follow this structure for every AskUserQuestion call:**
|
||||
@@ -148,6 +176,27 @@ ATTEMPTED: [what you tried]
|
||||
RECOMMENDATION: [what the user should do next]
|
||||
```
|
||||
|
||||
## Telemetry (run last)
|
||||
|
||||
After the skill workflow completes (success, error, or abort), log the telemetry event.
|
||||
Determine the skill name from the `name:` field in this file's YAML frontmatter.
|
||||
Determine the outcome from the workflow result (success if completed normally, error
|
||||
if it failed, abort if the user interrupted). Run this bash:
|
||||
|
||||
```bash
|
||||
_TEL_END=$(date +%s)
|
||||
_TEL_DUR=$(( _TEL_END - _TEL_START ))
|
||||
rm -f ~/.gstack/analytics/.pending-"$_SESSION_ID" 2>/dev/null || true
|
||||
~/.codex/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 &
|
||||
```
|
||||
|
||||
Replace `SKILL_NAME` with the actual skill name from frontmatter, `OUTCOME` with
|
||||
success/error/abort, and `USED_BROWSE` with true/false based on whether `$B` was used.
|
||||
If you cannot determine the outcome, use "unknown". This runs in the background and
|
||||
never blocks the user.
|
||||
|
||||
## Step 0: Detect base branch
|
||||
|
||||
Determine which branch this PR targets. Use the result as "the base branch" in all subsequent steps.
|
||||
|
||||
@@ -27,8 +27,15 @@ echo "BRANCH: $_BRANCH"
|
||||
echo "PROACTIVE: $_PROACTIVE"
|
||||
_LAKE_SEEN=$([ -f ~/.gstack/.completeness-intro-seen ] && echo "yes" || echo "no")
|
||||
echo "LAKE_INTRO: $_LAKE_SEEN"
|
||||
_TEL=$(~/.codex/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"
|
||||
mkdir -p ~/.gstack/analytics
|
||||
echo '{"skill":"plan-eng-review","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
|
||||
for _PF in ~/.gstack/analytics/.pending-*; do [ -f "$_PF" ] && ~/.codex/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown --session-id "$_SESSION_ID" 2>/dev/null || true; break; done
|
||||
```
|
||||
|
||||
If `PROACTIVE` is `"false"`, do not proactively suggest gstack skills — only invoke
|
||||
@@ -48,6 +55,27 @@ touch ~/.gstack/.completeness-intro-seen
|
||||
|
||||
Only run `open` if the user says yes. Always run `touch` to mark as seen. This only happens once.
|
||||
|
||||
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: After the lake intro is handled,
|
||||
ask the user about telemetry. Use AskUserQuestion:
|
||||
|
||||
> gstack can share anonymous usage data (which skills you use, how long they take, crash info)
|
||||
> to help improve the project. No code, file paths, or repo names are ever sent.
|
||||
> Change anytime with `gstack-config set telemetry off`.
|
||||
|
||||
Options:
|
||||
- A) Yes, share anonymous data (recommended)
|
||||
- B) No thanks
|
||||
|
||||
If A: run `~/.codex/skills/gstack/bin/gstack-config set telemetry anonymous`
|
||||
If B: run `~/.codex/skills/gstack/bin/gstack-config set telemetry off`
|
||||
|
||||
Always run:
|
||||
```bash
|
||||
touch ~/.gstack/.telemetry-prompted
|
||||
```
|
||||
|
||||
This only happens once. If `TEL_PROMPTED` is `yes`, skip this entirely.
|
||||
|
||||
## AskUserQuestion Format
|
||||
|
||||
**ALWAYS follow this structure for every AskUserQuestion call:**
|
||||
@@ -147,6 +175,27 @@ ATTEMPTED: [what you tried]
|
||||
RECOMMENDATION: [what the user should do next]
|
||||
```
|
||||
|
||||
## Telemetry (run last)
|
||||
|
||||
After the skill workflow completes (success, error, or abort), log the telemetry event.
|
||||
Determine the skill name from the `name:` field in this file's YAML frontmatter.
|
||||
Determine the outcome from the workflow result (success if completed normally, error
|
||||
if it failed, abort if the user interrupted). Run this bash:
|
||||
|
||||
```bash
|
||||
_TEL_END=$(date +%s)
|
||||
_TEL_DUR=$(( _TEL_END - _TEL_START ))
|
||||
rm -f ~/.gstack/analytics/.pending-"$_SESSION_ID" 2>/dev/null || true
|
||||
~/.codex/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 &
|
||||
```
|
||||
|
||||
Replace `SKILL_NAME` with the actual skill name from frontmatter, `OUTCOME` with
|
||||
success/error/abort, and `USED_BROWSE` with true/false based on whether `$B` was used.
|
||||
If you cannot determine the outcome, use "unknown". This runs in the background and
|
||||
never blocks the user.
|
||||
|
||||
# Plan Review Mode
|
||||
|
||||
Review this plan thoroughly before making any code changes. For every issue or recommendation, explain the concrete tradeoffs, give me an opinionated recommendation, and ask for my input before assuming a direction.
|
||||
|
||||
@@ -26,8 +26,15 @@ echo "BRANCH: $_BRANCH"
|
||||
echo "PROACTIVE: $_PROACTIVE"
|
||||
_LAKE_SEEN=$([ -f ~/.gstack/.completeness-intro-seen ] && echo "yes" || echo "no")
|
||||
echo "LAKE_INTRO: $_LAKE_SEEN"
|
||||
_TEL=$(~/.codex/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"
|
||||
mkdir -p ~/.gstack/analytics
|
||||
echo '{"skill":"qa-only","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
|
||||
for _PF in ~/.gstack/analytics/.pending-*; do [ -f "$_PF" ] && ~/.codex/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown --session-id "$_SESSION_ID" 2>/dev/null || true; break; done
|
||||
```
|
||||
|
||||
If `PROACTIVE` is `"false"`, do not proactively suggest gstack skills — only invoke
|
||||
@@ -47,6 +54,27 @@ touch ~/.gstack/.completeness-intro-seen
|
||||
|
||||
Only run `open` if the user says yes. Always run `touch` to mark as seen. This only happens once.
|
||||
|
||||
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: After the lake intro is handled,
|
||||
ask the user about telemetry. Use AskUserQuestion:
|
||||
|
||||
> gstack can share anonymous usage data (which skills you use, how long they take, crash info)
|
||||
> to help improve the project. No code, file paths, or repo names are ever sent.
|
||||
> Change anytime with `gstack-config set telemetry off`.
|
||||
|
||||
Options:
|
||||
- A) Yes, share anonymous data (recommended)
|
||||
- B) No thanks
|
||||
|
||||
If A: run `~/.codex/skills/gstack/bin/gstack-config set telemetry anonymous`
|
||||
If B: run `~/.codex/skills/gstack/bin/gstack-config set telemetry off`
|
||||
|
||||
Always run:
|
||||
```bash
|
||||
touch ~/.gstack/.telemetry-prompted
|
||||
```
|
||||
|
||||
This only happens once. If `TEL_PROMPTED` is `yes`, skip this entirely.
|
||||
|
||||
## AskUserQuestion Format
|
||||
|
||||
**ALWAYS follow this structure for every AskUserQuestion call:**
|
||||
@@ -146,6 +174,27 @@ ATTEMPTED: [what you tried]
|
||||
RECOMMENDATION: [what the user should do next]
|
||||
```
|
||||
|
||||
## Telemetry (run last)
|
||||
|
||||
After the skill workflow completes (success, error, or abort), log the telemetry event.
|
||||
Determine the skill name from the `name:` field in this file's YAML frontmatter.
|
||||
Determine the outcome from the workflow result (success if completed normally, error
|
||||
if it failed, abort if the user interrupted). Run this bash:
|
||||
|
||||
```bash
|
||||
_TEL_END=$(date +%s)
|
||||
_TEL_DUR=$(( _TEL_END - _TEL_START ))
|
||||
rm -f ~/.gstack/analytics/.pending-"$_SESSION_ID" 2>/dev/null || true
|
||||
~/.codex/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 &
|
||||
```
|
||||
|
||||
Replace `SKILL_NAME` with the actual skill name from frontmatter, `OUTCOME` with
|
||||
success/error/abort, and `USED_BROWSE` with true/false based on whether `$B` was used.
|
||||
If you cannot determine the outcome, use "unknown". This runs in the background and
|
||||
never blocks the user.
|
||||
|
||||
# /qa-only: Report-Only QA Testing
|
||||
|
||||
You are a QA engineer. Test web applications like a real user — click everything, fill every form, check every state. Produce a structured report with evidence. **NEVER fix anything.**
|
||||
|
||||
@@ -29,8 +29,15 @@ echo "BRANCH: $_BRANCH"
|
||||
echo "PROACTIVE: $_PROACTIVE"
|
||||
_LAKE_SEEN=$([ -f ~/.gstack/.completeness-intro-seen ] && echo "yes" || echo "no")
|
||||
echo "LAKE_INTRO: $_LAKE_SEEN"
|
||||
_TEL=$(~/.codex/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"
|
||||
mkdir -p ~/.gstack/analytics
|
||||
echo '{"skill":"qa","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
|
||||
for _PF in ~/.gstack/analytics/.pending-*; do [ -f "$_PF" ] && ~/.codex/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown --session-id "$_SESSION_ID" 2>/dev/null || true; break; done
|
||||
```
|
||||
|
||||
If `PROACTIVE` is `"false"`, do not proactively suggest gstack skills — only invoke
|
||||
@@ -50,6 +57,27 @@ touch ~/.gstack/.completeness-intro-seen
|
||||
|
||||
Only run `open` if the user says yes. Always run `touch` to mark as seen. This only happens once.
|
||||
|
||||
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: After the lake intro is handled,
|
||||
ask the user about telemetry. Use AskUserQuestion:
|
||||
|
||||
> gstack can share anonymous usage data (which skills you use, how long they take, crash info)
|
||||
> to help improve the project. No code, file paths, or repo names are ever sent.
|
||||
> Change anytime with `gstack-config set telemetry off`.
|
||||
|
||||
Options:
|
||||
- A) Yes, share anonymous data (recommended)
|
||||
- B) No thanks
|
||||
|
||||
If A: run `~/.codex/skills/gstack/bin/gstack-config set telemetry anonymous`
|
||||
If B: run `~/.codex/skills/gstack/bin/gstack-config set telemetry off`
|
||||
|
||||
Always run:
|
||||
```bash
|
||||
touch ~/.gstack/.telemetry-prompted
|
||||
```
|
||||
|
||||
This only happens once. If `TEL_PROMPTED` is `yes`, skip this entirely.
|
||||
|
||||
## AskUserQuestion Format
|
||||
|
||||
**ALWAYS follow this structure for every AskUserQuestion call:**
|
||||
@@ -149,6 +177,27 @@ ATTEMPTED: [what you tried]
|
||||
RECOMMENDATION: [what the user should do next]
|
||||
```
|
||||
|
||||
## Telemetry (run last)
|
||||
|
||||
After the skill workflow completes (success, error, or abort), log the telemetry event.
|
||||
Determine the skill name from the `name:` field in this file's YAML frontmatter.
|
||||
Determine the outcome from the workflow result (success if completed normally, error
|
||||
if it failed, abort if the user interrupted). Run this bash:
|
||||
|
||||
```bash
|
||||
_TEL_END=$(date +%s)
|
||||
_TEL_DUR=$(( _TEL_END - _TEL_START ))
|
||||
rm -f ~/.gstack/analytics/.pending-"$_SESSION_ID" 2>/dev/null || true
|
||||
~/.codex/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 &
|
||||
```
|
||||
|
||||
Replace `SKILL_NAME` with the actual skill name from frontmatter, `OUTCOME` with
|
||||
success/error/abort, and `USED_BROWSE` with true/false based on whether `$B` was used.
|
||||
If you cannot determine the outcome, use "unknown". This runs in the background and
|
||||
never blocks the user.
|
||||
|
||||
## Step 0: Detect base branch
|
||||
|
||||
Determine which branch this PR targets. Use the result as "the base branch" in all subsequent steps.
|
||||
|
||||
@@ -26,8 +26,15 @@ echo "BRANCH: $_BRANCH"
|
||||
echo "PROACTIVE: $_PROACTIVE"
|
||||
_LAKE_SEEN=$([ -f ~/.gstack/.completeness-intro-seen ] && echo "yes" || echo "no")
|
||||
echo "LAKE_INTRO: $_LAKE_SEEN"
|
||||
_TEL=$(~/.codex/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"
|
||||
mkdir -p ~/.gstack/analytics
|
||||
echo '{"skill":"retro","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
|
||||
for _PF in ~/.gstack/analytics/.pending-*; do [ -f "$_PF" ] && ~/.codex/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown --session-id "$_SESSION_ID" 2>/dev/null || true; break; done
|
||||
```
|
||||
|
||||
If `PROACTIVE` is `"false"`, do not proactively suggest gstack skills — only invoke
|
||||
@@ -47,6 +54,27 @@ touch ~/.gstack/.completeness-intro-seen
|
||||
|
||||
Only run `open` if the user says yes. Always run `touch` to mark as seen. This only happens once.
|
||||
|
||||
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: After the lake intro is handled,
|
||||
ask the user about telemetry. Use AskUserQuestion:
|
||||
|
||||
> gstack can share anonymous usage data (which skills you use, how long they take, crash info)
|
||||
> to help improve the project. No code, file paths, or repo names are ever sent.
|
||||
> Change anytime with `gstack-config set telemetry off`.
|
||||
|
||||
Options:
|
||||
- A) Yes, share anonymous data (recommended)
|
||||
- B) No thanks
|
||||
|
||||
If A: run `~/.codex/skills/gstack/bin/gstack-config set telemetry anonymous`
|
||||
If B: run `~/.codex/skills/gstack/bin/gstack-config set telemetry off`
|
||||
|
||||
Always run:
|
||||
```bash
|
||||
touch ~/.gstack/.telemetry-prompted
|
||||
```
|
||||
|
||||
This only happens once. If `TEL_PROMPTED` is `yes`, skip this entirely.
|
||||
|
||||
## AskUserQuestion Format
|
||||
|
||||
**ALWAYS follow this structure for every AskUserQuestion call:**
|
||||
@@ -146,6 +174,27 @@ ATTEMPTED: [what you tried]
|
||||
RECOMMENDATION: [what the user should do next]
|
||||
```
|
||||
|
||||
## Telemetry (run last)
|
||||
|
||||
After the skill workflow completes (success, error, or abort), log the telemetry event.
|
||||
Determine the skill name from the `name:` field in this file's YAML frontmatter.
|
||||
Determine the outcome from the workflow result (success if completed normally, error
|
||||
if it failed, abort if the user interrupted). Run this bash:
|
||||
|
||||
```bash
|
||||
_TEL_END=$(date +%s)
|
||||
_TEL_DUR=$(( _TEL_END - _TEL_START ))
|
||||
rm -f ~/.gstack/analytics/.pending-"$_SESSION_ID" 2>/dev/null || true
|
||||
~/.codex/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 &
|
||||
```
|
||||
|
||||
Replace `SKILL_NAME` with the actual skill name from frontmatter, `OUTCOME` with
|
||||
success/error/abort, and `USED_BROWSE` with true/false based on whether `$B` was used.
|
||||
If you cannot determine the outcome, use "unknown". This runs in the background and
|
||||
never blocks the user.
|
||||
|
||||
## Detect default branch
|
||||
|
||||
Before gathering data, detect the repo's default branch name:
|
||||
@@ -238,6 +287,9 @@ find . -name '*.test.*' -o -name '*.spec.*' -o -name '*_test.*' -o -name '*_spec
|
||||
# 11. Regression test commits in window
|
||||
git log origin/<default> --since="<window>" --oneline --grep="test(qa):" --grep="test(design):" --grep="test: coverage"
|
||||
|
||||
# 12. gstack skill usage telemetry (if available)
|
||||
cat ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true
|
||||
|
||||
# 12. Test files changed in window
|
||||
git log origin/<default> --since="<window>" --format="" --name-only | grep -E '\.(test|spec)\.' | sort -u | wc -l
|
||||
```
|
||||
|
||||
@@ -25,8 +25,15 @@ echo "BRANCH: $_BRANCH"
|
||||
echo "PROACTIVE: $_PROACTIVE"
|
||||
_LAKE_SEEN=$([ -f ~/.gstack/.completeness-intro-seen ] && echo "yes" || echo "no")
|
||||
echo "LAKE_INTRO: $_LAKE_SEEN"
|
||||
_TEL=$(~/.codex/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"
|
||||
mkdir -p ~/.gstack/analytics
|
||||
echo '{"skill":"review","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
|
||||
for _PF in ~/.gstack/analytics/.pending-*; do [ -f "$_PF" ] && ~/.codex/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown --session-id "$_SESSION_ID" 2>/dev/null || true; break; done
|
||||
```
|
||||
|
||||
If `PROACTIVE` is `"false"`, do not proactively suggest gstack skills — only invoke
|
||||
@@ -46,6 +53,27 @@ touch ~/.gstack/.completeness-intro-seen
|
||||
|
||||
Only run `open` if the user says yes. Always run `touch` to mark as seen. This only happens once.
|
||||
|
||||
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: After the lake intro is handled,
|
||||
ask the user about telemetry. Use AskUserQuestion:
|
||||
|
||||
> gstack can share anonymous usage data (which skills you use, how long they take, crash info)
|
||||
> to help improve the project. No code, file paths, or repo names are ever sent.
|
||||
> Change anytime with `gstack-config set telemetry off`.
|
||||
|
||||
Options:
|
||||
- A) Yes, share anonymous data (recommended)
|
||||
- B) No thanks
|
||||
|
||||
If A: run `~/.codex/skills/gstack/bin/gstack-config set telemetry anonymous`
|
||||
If B: run `~/.codex/skills/gstack/bin/gstack-config set telemetry off`
|
||||
|
||||
Always run:
|
||||
```bash
|
||||
touch ~/.gstack/.telemetry-prompted
|
||||
```
|
||||
|
||||
This only happens once. If `TEL_PROMPTED` is `yes`, skip this entirely.
|
||||
|
||||
## AskUserQuestion Format
|
||||
|
||||
**ALWAYS follow this structure for every AskUserQuestion call:**
|
||||
@@ -145,6 +173,27 @@ ATTEMPTED: [what you tried]
|
||||
RECOMMENDATION: [what the user should do next]
|
||||
```
|
||||
|
||||
## Telemetry (run last)
|
||||
|
||||
After the skill workflow completes (success, error, or abort), log the telemetry event.
|
||||
Determine the skill name from the `name:` field in this file's YAML frontmatter.
|
||||
Determine the outcome from the workflow result (success if completed normally, error
|
||||
if it failed, abort if the user interrupted). Run this bash:
|
||||
|
||||
```bash
|
||||
_TEL_END=$(date +%s)
|
||||
_TEL_DUR=$(( _TEL_END - _TEL_START ))
|
||||
rm -f ~/.gstack/analytics/.pending-"$_SESSION_ID" 2>/dev/null || true
|
||||
~/.codex/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 &
|
||||
```
|
||||
|
||||
Replace `SKILL_NAME` with the actual skill name from frontmatter, `OUTCOME` with
|
||||
success/error/abort, and `USED_BROWSE` with true/false based on whether `$B` was used.
|
||||
If you cannot determine the outcome, use "unknown". This runs in the background and
|
||||
never blocks the user.
|
||||
|
||||
## Step 0: Detect base branch
|
||||
|
||||
Determine which branch this PR targets. Use the result as "the base branch" in all subsequent steps.
|
||||
|
||||
@@ -25,8 +25,15 @@ echo "BRANCH: $_BRANCH"
|
||||
echo "PROACTIVE: $_PROACTIVE"
|
||||
_LAKE_SEEN=$([ -f ~/.gstack/.completeness-intro-seen ] && echo "yes" || echo "no")
|
||||
echo "LAKE_INTRO: $_LAKE_SEEN"
|
||||
_TEL=$(~/.codex/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"
|
||||
mkdir -p ~/.gstack/analytics
|
||||
echo '{"skill":"setup-browser-cookies","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
|
||||
for _PF in ~/.gstack/analytics/.pending-*; do [ -f "$_PF" ] && ~/.codex/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown --session-id "$_SESSION_ID" 2>/dev/null || true; break; done
|
||||
```
|
||||
|
||||
If `PROACTIVE` is `"false"`, do not proactively suggest gstack skills — only invoke
|
||||
@@ -46,6 +53,27 @@ touch ~/.gstack/.completeness-intro-seen
|
||||
|
||||
Only run `open` if the user says yes. Always run `touch` to mark as seen. This only happens once.
|
||||
|
||||
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: After the lake intro is handled,
|
||||
ask the user about telemetry. Use AskUserQuestion:
|
||||
|
||||
> gstack can share anonymous usage data (which skills you use, how long they take, crash info)
|
||||
> to help improve the project. No code, file paths, or repo names are ever sent.
|
||||
> Change anytime with `gstack-config set telemetry off`.
|
||||
|
||||
Options:
|
||||
- A) Yes, share anonymous data (recommended)
|
||||
- B) No thanks
|
||||
|
||||
If A: run `~/.codex/skills/gstack/bin/gstack-config set telemetry anonymous`
|
||||
If B: run `~/.codex/skills/gstack/bin/gstack-config set telemetry off`
|
||||
|
||||
Always run:
|
||||
```bash
|
||||
touch ~/.gstack/.telemetry-prompted
|
||||
```
|
||||
|
||||
This only happens once. If `TEL_PROMPTED` is `yes`, skip this entirely.
|
||||
|
||||
## AskUserQuestion Format
|
||||
|
||||
**ALWAYS follow this structure for every AskUserQuestion call:**
|
||||
@@ -145,6 +173,27 @@ ATTEMPTED: [what you tried]
|
||||
RECOMMENDATION: [what the user should do next]
|
||||
```
|
||||
|
||||
## Telemetry (run last)
|
||||
|
||||
After the skill workflow completes (success, error, or abort), log the telemetry event.
|
||||
Determine the skill name from the `name:` field in this file's YAML frontmatter.
|
||||
Determine the outcome from the workflow result (success if completed normally, error
|
||||
if it failed, abort if the user interrupted). Run this bash:
|
||||
|
||||
```bash
|
||||
_TEL_END=$(date +%s)
|
||||
_TEL_DUR=$(( _TEL_END - _TEL_START ))
|
||||
rm -f ~/.gstack/analytics/.pending-"$_SESSION_ID" 2>/dev/null || true
|
||||
~/.codex/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 &
|
||||
```
|
||||
|
||||
Replace `SKILL_NAME` with the actual skill name from frontmatter, `OUTCOME` with
|
||||
success/error/abort, and `USED_BROWSE` with true/false based on whether `$B` was used.
|
||||
If you cannot determine the outcome, use "unknown". This runs in the background and
|
||||
never blocks the user.
|
||||
|
||||
# Setup Browser Cookies
|
||||
|
||||
Import logged-in sessions from your real Chromium browser into the headless browse session.
|
||||
|
||||
@@ -23,8 +23,15 @@ echo "BRANCH: $_BRANCH"
|
||||
echo "PROACTIVE: $_PROACTIVE"
|
||||
_LAKE_SEEN=$([ -f ~/.gstack/.completeness-intro-seen ] && echo "yes" || echo "no")
|
||||
echo "LAKE_INTRO: $_LAKE_SEEN"
|
||||
_TEL=$(~/.codex/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"
|
||||
mkdir -p ~/.gstack/analytics
|
||||
echo '{"skill":"ship","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
|
||||
for _PF in ~/.gstack/analytics/.pending-*; do [ -f "$_PF" ] && ~/.codex/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown --session-id "$_SESSION_ID" 2>/dev/null || true; break; done
|
||||
```
|
||||
|
||||
If `PROACTIVE` is `"false"`, do not proactively suggest gstack skills — only invoke
|
||||
@@ -44,6 +51,27 @@ touch ~/.gstack/.completeness-intro-seen
|
||||
|
||||
Only run `open` if the user says yes. Always run `touch` to mark as seen. This only happens once.
|
||||
|
||||
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: After the lake intro is handled,
|
||||
ask the user about telemetry. Use AskUserQuestion:
|
||||
|
||||
> gstack can share anonymous usage data (which skills you use, how long they take, crash info)
|
||||
> to help improve the project. No code, file paths, or repo names are ever sent.
|
||||
> Change anytime with `gstack-config set telemetry off`.
|
||||
|
||||
Options:
|
||||
- A) Yes, share anonymous data (recommended)
|
||||
- B) No thanks
|
||||
|
||||
If A: run `~/.codex/skills/gstack/bin/gstack-config set telemetry anonymous`
|
||||
If B: run `~/.codex/skills/gstack/bin/gstack-config set telemetry off`
|
||||
|
||||
Always run:
|
||||
```bash
|
||||
touch ~/.gstack/.telemetry-prompted
|
||||
```
|
||||
|
||||
This only happens once. If `TEL_PROMPTED` is `yes`, skip this entirely.
|
||||
|
||||
## AskUserQuestion Format
|
||||
|
||||
**ALWAYS follow this structure for every AskUserQuestion call:**
|
||||
@@ -143,6 +171,27 @@ ATTEMPTED: [what you tried]
|
||||
RECOMMENDATION: [what the user should do next]
|
||||
```
|
||||
|
||||
## Telemetry (run last)
|
||||
|
||||
After the skill workflow completes (success, error, or abort), log the telemetry event.
|
||||
Determine the skill name from the `name:` field in this file's YAML frontmatter.
|
||||
Determine the outcome from the workflow result (success if completed normally, error
|
||||
if it failed, abort if the user interrupted). Run this bash:
|
||||
|
||||
```bash
|
||||
_TEL_END=$(date +%s)
|
||||
_TEL_DUR=$(( _TEL_END - _TEL_START ))
|
||||
rm -f ~/.gstack/analytics/.pending-"$_SESSION_ID" 2>/dev/null || true
|
||||
~/.codex/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 &
|
||||
```
|
||||
|
||||
Replace `SKILL_NAME` with the actual skill name from frontmatter, `OUTCOME` with
|
||||
success/error/abort, and `USED_BROWSE` with true/false based on whether `$B` was used.
|
||||
If you cannot determine the outcome, use "unknown". This runs in the background and
|
||||
never blocks the user.
|
||||
|
||||
## Step 0: Detect base branch
|
||||
|
||||
Determine which branch this PR targets. Use the result as "the base branch" in all subsequent steps.
|
||||
|
||||
@@ -58,8 +58,15 @@ echo "BRANCH: $_BRANCH"
|
||||
echo "PROACTIVE: $_PROACTIVE"
|
||||
_LAKE_SEEN=$([ -f ~/.gstack/.completeness-intro-seen ] && echo "yes" || echo "no")
|
||||
echo "LAKE_INTRO: $_LAKE_SEEN"
|
||||
_TEL=$(~/.codex/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"
|
||||
mkdir -p ~/.gstack/analytics
|
||||
echo '{"skill":"gstack","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
|
||||
for _PF in ~/.gstack/analytics/.pending-*; do [ -f "$_PF" ] && ~/.codex/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown --session-id "$_SESSION_ID" 2>/dev/null || true; break; done
|
||||
```
|
||||
|
||||
If `PROACTIVE` is `"false"`, do not proactively suggest gstack skills — only invoke
|
||||
@@ -79,6 +86,27 @@ touch ~/.gstack/.completeness-intro-seen
|
||||
|
||||
Only run `open` if the user says yes. Always run `touch` to mark as seen. This only happens once.
|
||||
|
||||
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: After the lake intro is handled,
|
||||
ask the user about telemetry. Use AskUserQuestion:
|
||||
|
||||
> gstack can share anonymous usage data (which skills you use, how long they take, crash info)
|
||||
> to help improve the project. No code, file paths, or repo names are ever sent.
|
||||
> Change anytime with `gstack-config set telemetry off`.
|
||||
|
||||
Options:
|
||||
- A) Yes, share anonymous data (recommended)
|
||||
- B) No thanks
|
||||
|
||||
If A: run `~/.codex/skills/gstack/bin/gstack-config set telemetry anonymous`
|
||||
If B: run `~/.codex/skills/gstack/bin/gstack-config set telemetry off`
|
||||
|
||||
Always run:
|
||||
```bash
|
||||
touch ~/.gstack/.telemetry-prompted
|
||||
```
|
||||
|
||||
This only happens once. If `TEL_PROMPTED` is `yes`, skip this entirely.
|
||||
|
||||
## AskUserQuestion Format
|
||||
|
||||
**ALWAYS follow this structure for every AskUserQuestion call:**
|
||||
@@ -178,6 +206,27 @@ ATTEMPTED: [what you tried]
|
||||
RECOMMENDATION: [what the user should do next]
|
||||
```
|
||||
|
||||
## Telemetry (run last)
|
||||
|
||||
After the skill workflow completes (success, error, or abort), log the telemetry event.
|
||||
Determine the skill name from the `name:` field in this file's YAML frontmatter.
|
||||
Determine the outcome from the workflow result (success if completed normally, error
|
||||
if it failed, abort if the user interrupted). Run this bash:
|
||||
|
||||
```bash
|
||||
_TEL_END=$(date +%s)
|
||||
_TEL_DUR=$(( _TEL_END - _TEL_START ))
|
||||
rm -f ~/.gstack/analytics/.pending-"$_SESSION_ID" 2>/dev/null || true
|
||||
~/.codex/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 &
|
||||
```
|
||||
|
||||
Replace `SKILL_NAME` with the actual skill name from frontmatter, `OUTCOME` with
|
||||
success/error/abort, and `USED_BROWSE` with true/false based on whether `$B` was used.
|
||||
If you cannot determine the outcome, use "unknown". This runs in the background and
|
||||
never blocks the user.
|
||||
|
||||
If `PROACTIVE` is `false`: do NOT proactively suggest other gstack skills during this session.
|
||||
Only run skills the user explicitly invokes. This preference persists across sessions via
|
||||
`gstack-config`.
|
||||
|
||||
@@ -9,6 +9,19 @@
|
||||
- **Codex-adapted output.** Frontmatter is stripped to just name + description (Codex doesn't need allowed-tools or hooks). Paths are rewritten from `~/.claude/` to `~/.codex/`. The `/codex` skill itself is excluded from Codex output -- it's a Claude wrapper around `codex exec`, which would be self-referential.
|
||||
- **CI checks both hosts.** The freshness check now validates Claude and Codex output independently. Stale Codex docs break the build just like stale Claude docs.
|
||||
|
||||
## [0.8.6] - 2026-03-19
|
||||
|
||||
### Added
|
||||
|
||||
- **You can now see how you use gstack.** Run `gstack-analytics` to see a personal usage dashboard — which skills you use most, how long they take, your success rate. All data stays local on your machine.
|
||||
- **Opt-in community telemetry.** On first run, gstack asks if you want to share anonymous usage data (skill names, duration, crash info — never code or file paths). Choose "yes" and you're part of the community pulse. Change anytime with `gstack-config set telemetry off`.
|
||||
- **Community health dashboard.** Run `gstack-community-dashboard` to see what the gstack community is building — most popular skills, crash clusters, version distribution. All powered by Supabase.
|
||||
- **Install base tracking via update check.** When telemetry is enabled, gstack fires a parallel ping to Supabase during update checks — giving us an install-base count without adding any latency. Respects your telemetry setting (default off). GitHub remains the primary version source.
|
||||
- **Crash clustering.** Errors are automatically grouped by type and version in the Supabase backend, so the most impactful bugs surface first.
|
||||
- **Upgrade funnel tracking.** We can now see how many people see upgrade prompts vs actually upgrade — helps us ship better releases.
|
||||
- **/retro now shows your gstack usage.** Weekly retrospectives include skill usage stats (which skills you used, how often, success rate) alongside your commit history.
|
||||
- **Session-specific pending markers.** If a skill crashes mid-run, the next invocation correctly finalizes only that session — no more race conditions between concurrent gstack sessions.
|
||||
|
||||
## [0.8.5] - 2026-03-19
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -216,6 +216,20 @@ Fifteen specialists and six power tools. All slash commands. All Markdown. All f
|
||||
| [Contributing](CONTRIBUTING.md) | Dev setup, testing, contributor mode, and dev mode |
|
||||
| [Changelog](CHANGELOG.md) | What's new in every version |
|
||||
|
||||
## Privacy & Telemetry
|
||||
|
||||
gstack includes **opt-in** usage telemetry to help improve the project. Here's exactly what happens:
|
||||
|
||||
- **Default is off.** Nothing is sent anywhere unless you explicitly say yes.
|
||||
- **On first run,** gstack asks if you want to share anonymous usage data. You can say no.
|
||||
- **What's sent (if you opt in):** skill name, duration, success/fail, gstack version, OS. That's it.
|
||||
- **What's never sent:** code, file paths, repo names, branch names, prompts, or any user-generated content.
|
||||
- **Change anytime:** `gstack-config set telemetry off` disables everything instantly.
|
||||
|
||||
Data is stored in [Supabase](https://supabase.com) (open source Firebase alternative). The schema is in [`supabase/migrations/001_telemetry.sql`](supabase/migrations/001_telemetry.sql) — you can verify exactly what's collected. The Supabase publishable key in the repo is a public key (like a Firebase API key) — row-level security policies restrict it to insert-only access.
|
||||
|
||||
**Local analytics are always available.** Run `gstack-analytics` to see your personal usage dashboard from the local JSONL file — no remote data needed.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**Skill not showing up?** `cd ~/.claude/skills/gstack && ./setup`
|
||||
|
||||
@@ -64,8 +64,15 @@ echo "BRANCH: $_BRANCH"
|
||||
echo "PROACTIVE: $_PROACTIVE"
|
||||
_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"
|
||||
mkdir -p ~/.gstack/analytics
|
||||
echo '{"skill":"gstack","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
|
||||
for _PF in ~/.gstack/analytics/.pending-*; do [ -f "$_PF" ] && ~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown --session-id "$_SESSION_ID" 2>/dev/null || true; break; done
|
||||
```
|
||||
|
||||
If `PROACTIVE` is `"false"`, do not proactively suggest gstack skills — only invoke
|
||||
@@ -85,6 +92,27 @@ touch ~/.gstack/.completeness-intro-seen
|
||||
|
||||
Only run `open` if the user says yes. Always run `touch` to mark as seen. This only happens once.
|
||||
|
||||
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: After the lake intro is handled,
|
||||
ask the user about telemetry. Use AskUserQuestion:
|
||||
|
||||
> gstack can share anonymous usage data (which skills you use, how long they take, crash info)
|
||||
> to help improve the project. No code, file paths, or repo names are ever sent.
|
||||
> Change anytime with `gstack-config set telemetry off`.
|
||||
|
||||
Options:
|
||||
- A) Yes, share anonymous data (recommended)
|
||||
- B) No thanks
|
||||
|
||||
If A: run `~/.claude/skills/gstack/bin/gstack-config set telemetry anonymous`
|
||||
If B: run `~/.claude/skills/gstack/bin/gstack-config set telemetry off`
|
||||
|
||||
Always run:
|
||||
```bash
|
||||
touch ~/.gstack/.telemetry-prompted
|
||||
```
|
||||
|
||||
This only happens once. If `TEL_PROMPTED` is `yes`, skip this entirely.
|
||||
|
||||
## AskUserQuestion Format
|
||||
|
||||
**ALWAYS follow this structure for every AskUserQuestion call:**
|
||||
@@ -184,6 +212,27 @@ ATTEMPTED: [what you tried]
|
||||
RECOMMENDATION: [what the user should do next]
|
||||
```
|
||||
|
||||
## Telemetry (run last)
|
||||
|
||||
After the skill workflow completes (success, error, or abort), log the telemetry event.
|
||||
Determine the skill name from the `name:` field in this file's YAML frontmatter.
|
||||
Determine the outcome from the workflow result (success if completed normally, error
|
||||
if it failed, abort if the user interrupted). Run this bash:
|
||||
|
||||
```bash
|
||||
_TEL_END=$(date +%s)
|
||||
_TEL_DUR=$(( _TEL_END - _TEL_START ))
|
||||
rm -f ~/.gstack/analytics/.pending-"$_SESSION_ID" 2>/dev/null || true
|
||||
~/.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 &
|
||||
```
|
||||
|
||||
Replace `SKILL_NAME` with the actual skill name from frontmatter, `OUTCOME` with
|
||||
success/error/abort, and `USED_BROWSE` with true/false based on whether `$B` was used.
|
||||
If you cannot determine the outcome, use "unknown". This runs in the background and
|
||||
never blocks the user.
|
||||
|
||||
If `PROACTIVE` is `false`: do NOT proactively suggest other gstack skills during this session.
|
||||
Only run skills the user explicitly invokes. This preference persists across sessions via
|
||||
`gstack-config`.
|
||||
|
||||
Executable
+191
@@ -0,0 +1,191 @@
|
||||
#!/usr/bin/env bash
|
||||
# gstack-analytics — personal usage dashboard from local JSONL
|
||||
#
|
||||
# Usage:
|
||||
# gstack-analytics # default: last 7 days
|
||||
# gstack-analytics 7d # last 7 days
|
||||
# gstack-analytics 30d # last 30 days
|
||||
# gstack-analytics all # all time
|
||||
#
|
||||
# Env overrides (for testing):
|
||||
# GSTACK_STATE_DIR — override ~/.gstack state directory
|
||||
set -uo pipefail
|
||||
|
||||
STATE_DIR="${GSTACK_STATE_DIR:-$HOME/.gstack}"
|
||||
JSONL_FILE="$STATE_DIR/analytics/skill-usage.jsonl"
|
||||
|
||||
# ─── Parse time window ───────────────────────────────────────
|
||||
WINDOW="${1:-7d}"
|
||||
case "$WINDOW" in
|
||||
7d) DAYS=7; LABEL="last 7 days" ;;
|
||||
30d) DAYS=30; LABEL="last 30 days" ;;
|
||||
all) DAYS=0; LABEL="all time" ;;
|
||||
*) DAYS=7; LABEL="last 7 days" ;;
|
||||
esac
|
||||
|
||||
# ─── Check for data ──────────────────────────────────────────
|
||||
if [ ! -f "$JSONL_FILE" ]; then
|
||||
echo "gstack usage — no data yet"
|
||||
echo ""
|
||||
echo "Usage data will appear here after you use gstack skills"
|
||||
echo "with telemetry enabled (gstack-config set telemetry anonymous)."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
TOTAL_LINES="$(wc -l < "$JSONL_FILE" | tr -d ' ')"
|
||||
if [ "$TOTAL_LINES" = "0" ]; then
|
||||
echo "gstack usage — no data yet"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# ─── Filter by time window ───────────────────────────────────
|
||||
if [ "$DAYS" -gt 0 ] 2>/dev/null; then
|
||||
# Calculate cutoff date
|
||||
if date -v-1d +%Y-%m-%d >/dev/null 2>&1; then
|
||||
# macOS date
|
||||
CUTOFF="$(date -v-${DAYS}d -u +%Y-%m-%dT%H:%M:%SZ)"
|
||||
else
|
||||
# GNU date
|
||||
CUTOFF="$(date -u -d "$DAYS days ago" +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || echo "2000-01-01T00:00:00Z")"
|
||||
fi
|
||||
# Filter: skill_run events (new format) OR basic skill events (old format, no event_type)
|
||||
# Old format: {"skill":"X","ts":"Y","repo":"Z"} (no event_type field)
|
||||
# New format: {"event_type":"skill_run","skill":"X","ts":"Y",...}
|
||||
FILTERED="$(awk -F'"' -v cutoff="$CUTOFF" '
|
||||
/"ts":"/ {
|
||||
# Skip hook_fire events
|
||||
if (/"event":"hook_fire"/) next
|
||||
# Skip non-skill_run new-format events
|
||||
if (/"event_type":"/ && !/"event_type":"skill_run"/) next
|
||||
for (i=1; i<=NF; i++) {
|
||||
if ($i == "ts" && $(i+1) ~ /^:/) {
|
||||
ts = $(i+2)
|
||||
if (ts >= cutoff) { print; break }
|
||||
}
|
||||
}
|
||||
}
|
||||
' "$JSONL_FILE")"
|
||||
else
|
||||
# All time: include skill_run events + old-format basic events, exclude hook_fire
|
||||
FILTERED="$(awk '/"ts":"/ && !/"event":"hook_fire"/' "$JSONL_FILE" | grep -v '"event_type":"upgrade_' 2>/dev/null || true)"
|
||||
fi
|
||||
|
||||
if [ -z "$FILTERED" ]; then
|
||||
echo "gstack usage ($LABEL) — no skill runs found"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# ─── Aggregate by skill ──────────────────────────────────────
|
||||
# Extract skill names and count
|
||||
SKILL_COUNTS="$(echo "$FILTERED" | awk -F'"' '
|
||||
/"skill":"/ {
|
||||
for (i=1; i<=NF; i++) {
|
||||
if ($i == "skill" && $(i+1) ~ /^:/) {
|
||||
skill = $(i+2)
|
||||
counts[skill]++
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
END {
|
||||
for (s in counts) print counts[s], s
|
||||
}
|
||||
' | sort -rn)"
|
||||
|
||||
# Count outcomes
|
||||
TOTAL="$(echo "$FILTERED" | wc -l | tr -d ' ')"
|
||||
SUCCESS="$(echo "$FILTERED" | grep -c '"outcome":"success"' || true)"
|
||||
SUCCESS="${SUCCESS:-0}"; SUCCESS="$(echo "$SUCCESS" | tr -d ' \n\r\t')"
|
||||
ERRORS="$(echo "$FILTERED" | grep -c '"outcome":"error"' || true)"
|
||||
ERRORS="${ERRORS:-0}"; ERRORS="$(echo "$ERRORS" | tr -d ' \n\r\t')"
|
||||
# Old format events have no outcome field — count them as successful
|
||||
NO_OUTCOME="$(echo "$FILTERED" | grep -vc '"outcome":' || true)"
|
||||
NO_OUTCOME="${NO_OUTCOME:-0}"; NO_OUTCOME="$(echo "$NO_OUTCOME" | tr -d ' \n\r\t')"
|
||||
SUCCESS=$(( SUCCESS + NO_OUTCOME ))
|
||||
|
||||
# Calculate success rate
|
||||
if [ "$TOTAL" -gt 0 ] 2>/dev/null; then
|
||||
SUCCESS_RATE=$(( SUCCESS * 100 / TOTAL ))
|
||||
else
|
||||
SUCCESS_RATE=100
|
||||
fi
|
||||
|
||||
# ─── Calculate total duration ────────────────────────────────
|
||||
TOTAL_DURATION="$(echo "$FILTERED" | awk -F'[:,]' '
|
||||
/"duration_s"/ {
|
||||
for (i=1; i<=NF; i++) {
|
||||
if ($i ~ /"duration_s"/) {
|
||||
val = $(i+1)
|
||||
gsub(/[^0-9.]/, "", val)
|
||||
if (val+0 > 0) total += val
|
||||
}
|
||||
}
|
||||
}
|
||||
END { printf "%.0f", total }
|
||||
')"
|
||||
|
||||
# Format duration
|
||||
TOTAL_DURATION="${TOTAL_DURATION:-0}"
|
||||
if [ "$TOTAL_DURATION" -ge 3600 ] 2>/dev/null; then
|
||||
HOURS=$(( TOTAL_DURATION / 3600 ))
|
||||
MINS=$(( (TOTAL_DURATION % 3600) / 60 ))
|
||||
DUR_DISPLAY="${HOURS}h ${MINS}m"
|
||||
elif [ "$TOTAL_DURATION" -ge 60 ] 2>/dev/null; then
|
||||
MINS=$(( TOTAL_DURATION / 60 ))
|
||||
DUR_DISPLAY="${MINS}m"
|
||||
else
|
||||
DUR_DISPLAY="${TOTAL_DURATION}s"
|
||||
fi
|
||||
|
||||
# ─── Render output ───────────────────────────────────────────
|
||||
echo "gstack usage ($LABEL)"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
|
||||
# Find max count for bar scaling
|
||||
MAX_COUNT="$(echo "$SKILL_COUNTS" | head -1 | awk '{print $1}')"
|
||||
BAR_WIDTH=20
|
||||
|
||||
echo "$SKILL_COUNTS" | while read -r COUNT SKILL; do
|
||||
# Scale bar
|
||||
if [ "$MAX_COUNT" -gt 0 ] 2>/dev/null; then
|
||||
BAR_LEN=$(( COUNT * BAR_WIDTH / MAX_COUNT ))
|
||||
else
|
||||
BAR_LEN=1
|
||||
fi
|
||||
[ "$BAR_LEN" -lt 1 ] && BAR_LEN=1
|
||||
|
||||
# Build bar
|
||||
BAR=""
|
||||
i=0
|
||||
while [ "$i" -lt "$BAR_LEN" ]; do
|
||||
BAR="${BAR}█"
|
||||
i=$(( i + 1 ))
|
||||
done
|
||||
|
||||
# Calculate avg duration for this skill
|
||||
AVG_DUR="$(echo "$FILTERED" | awk -v skill="$SKILL" '
|
||||
index($0, "\"skill\":\"" skill "\"") > 0 {
|
||||
# Extract duration_s value using split on "duration_s":
|
||||
n = split($0, parts, "\"duration_s\":")
|
||||
if (n >= 2) {
|
||||
# parts[2] starts with the value, e.g. "142,"
|
||||
gsub(/[^0-9.].*/, "", parts[2])
|
||||
if (parts[2]+0 > 0) { total += parts[2]; count++ }
|
||||
}
|
||||
}
|
||||
END { if (count > 0) printf "%.0f", total/count; else print "0" }
|
||||
')"
|
||||
|
||||
# Format avg duration
|
||||
if [ "$AVG_DUR" -ge 60 ] 2>/dev/null; then
|
||||
AVG_DISPLAY="$(( AVG_DUR / 60 ))m"
|
||||
else
|
||||
AVG_DISPLAY="${AVG_DUR}s"
|
||||
fi
|
||||
|
||||
printf " /%-20s %s %d runs (avg %s)\n" "$SKILL" "$BAR" "$COUNT" "$AVG_DISPLAY"
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "Success rate: ${SUCCESS_RATE}% | Errors: ${ERRORS} | Total time: ${DUR_DISPLAY}"
|
||||
echo "Events: ${TOTAL} skill runs"
|
||||
Executable
+113
@@ -0,0 +1,113 @@
|
||||
#!/usr/bin/env bash
|
||||
# gstack-community-dashboard — community usage stats from Supabase
|
||||
#
|
||||
# Queries the Supabase REST API to show community-wide gstack usage:
|
||||
# skill popularity, crash clusters, version distribution, retention.
|
||||
#
|
||||
# Env overrides (for testing):
|
||||
# GSTACK_DIR — override auto-detected gstack root
|
||||
# GSTACK_SUPABASE_URL — override Supabase project URL
|
||||
# GSTACK_SUPABASE_ANON_KEY — override Supabase anon key
|
||||
set -uo pipefail
|
||||
|
||||
GSTACK_DIR="${GSTACK_DIR:-$(cd "$(dirname "$0")/.." && pwd)}"
|
||||
|
||||
# Source Supabase config if not overridden by env
|
||||
if [ -z "${GSTACK_SUPABASE_URL:-}" ] && [ -f "$GSTACK_DIR/supabase/config.sh" ]; then
|
||||
. "$GSTACK_DIR/supabase/config.sh"
|
||||
fi
|
||||
SUPABASE_URL="${GSTACK_SUPABASE_URL:-}"
|
||||
ANON_KEY="${GSTACK_SUPABASE_ANON_KEY:-}"
|
||||
|
||||
if [ -z "$SUPABASE_URL" ] || [ -z "$ANON_KEY" ]; then
|
||||
echo "gstack community dashboard"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
echo "Supabase not configured yet. The community dashboard will be"
|
||||
echo "available once the gstack Supabase project is set up."
|
||||
echo ""
|
||||
echo "For local analytics, run: gstack-analytics"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# ─── Helper: query Supabase REST API ─────────────────────────
|
||||
query() {
|
||||
local table="$1"
|
||||
local params="${2:-}"
|
||||
curl -sf --max-time 10 \
|
||||
"${SUPABASE_URL}/rest/v1/${table}?${params}" \
|
||||
-H "apikey: ${ANON_KEY}" \
|
||||
-H "Authorization: Bearer ${ANON_KEY}" \
|
||||
2>/dev/null || echo "[]"
|
||||
}
|
||||
|
||||
echo "gstack community dashboard"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
|
||||
# ─── Weekly active installs ──────────────────────────────────
|
||||
WEEK_AGO="$(date -u -v-7d +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -u -d '7 days ago' +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || echo "")"
|
||||
if [ -n "$WEEK_AGO" ]; then
|
||||
PULSE="$(curl -sf --max-time 10 \
|
||||
"${SUPABASE_URL}/functions/v1/community-pulse" \
|
||||
-H "Authorization: Bearer ${ANON_KEY}" \
|
||||
2>/dev/null || echo '{"weekly_active":0}')"
|
||||
|
||||
WEEKLY="$(echo "$PULSE" | grep -o '"weekly_active":[0-9]*' | grep -o '[0-9]*' || echo "0")"
|
||||
CHANGE="$(echo "$PULSE" | grep -o '"change_pct":[0-9-]*' | grep -o '[0-9-]*' || echo "0")"
|
||||
|
||||
echo "Weekly active installs: ${WEEKLY}"
|
||||
if [ "$CHANGE" -gt 0 ] 2>/dev/null; then
|
||||
echo " Change: +${CHANGE}%"
|
||||
elif [ "$CHANGE" -lt 0 ] 2>/dev/null; then
|
||||
echo " Change: ${CHANGE}%"
|
||||
fi
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# ─── Skill popularity (top 10) ───────────────────────────────
|
||||
echo "Top skills (last 7 days)"
|
||||
echo "────────────────────────"
|
||||
|
||||
# Query telemetry_events, group by skill
|
||||
EVENTS="$(query "telemetry_events" "select=skill,gstack_version&event_type=eq.skill_run&event_timestamp=gte.${WEEK_AGO}&limit=1000" 2>/dev/null || echo "[]")"
|
||||
|
||||
if [ "$EVENTS" != "[]" ] && [ -n "$EVENTS" ]; then
|
||||
echo "$EVENTS" | grep -o '"skill":"[^"]*"' | awk -F'"' '{print $4}' | sort | uniq -c | sort -rn | head -10 | while read -r COUNT SKILL; do
|
||||
printf " /%-20s %d runs\n" "$SKILL" "$COUNT"
|
||||
done
|
||||
else
|
||||
echo " No data yet"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# ─── Crash clusters ──────────────────────────────────────────
|
||||
echo "Top crash clusters"
|
||||
echo "──────────────────"
|
||||
|
||||
CRASHES="$(query "crash_clusters" "select=error_class,gstack_version,total_occurrences,identified_users&limit=5" 2>/dev/null || echo "[]")"
|
||||
|
||||
if [ "$CRASHES" != "[]" ] && [ -n "$CRASHES" ]; then
|
||||
echo "$CRASHES" | grep -o '"error_class":"[^"]*"' | awk -F'"' '{print $4}' | head -5 | while read -r ERR; do
|
||||
C="$(echo "$CRASHES" | grep -o "\"error_class\":\"$ERR\"[^}]*\"total_occurrences\":[0-9]*" | grep -o '"total_occurrences":[0-9]*' | head -1 | grep -o '[0-9]*')"
|
||||
printf " %-30s %s occurrences\n" "$ERR" "${C:-?}"
|
||||
done
|
||||
else
|
||||
echo " No crashes reported"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# ─── Version distribution ────────────────────────────────────
|
||||
echo "Version distribution (last 7 days)"
|
||||
echo "───────────────────────────────────"
|
||||
|
||||
if [ "$EVENTS" != "[]" ] && [ -n "$EVENTS" ]; then
|
||||
echo "$EVENTS" | grep -o '"gstack_version":"[^"]*"' | awk -F'"' '{print $4}' | sort | uniq -c | sort -rn | head -5 | while read -r COUNT VER; do
|
||||
printf " v%-15s %d events\n" "$VER" "$COUNT"
|
||||
done
|
||||
else
|
||||
echo " No data yet"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "For local analytics: gstack-analytics"
|
||||
Executable
+158
@@ -0,0 +1,158 @@
|
||||
#!/usr/bin/env bash
|
||||
# gstack-telemetry-log — append a telemetry event to local JSONL
|
||||
#
|
||||
# Data flow:
|
||||
# preamble (start) ──▶ .pending marker
|
||||
# preamble (epilogue) ──▶ gstack-telemetry-log ──▶ skill-usage.jsonl
|
||||
# └──▶ gstack-telemetry-sync (bg)
|
||||
#
|
||||
# Usage:
|
||||
# gstack-telemetry-log --skill qa --duration 142 --outcome success \
|
||||
# --used-browse true --session-id "12345-1710756600"
|
||||
#
|
||||
# Env overrides (for testing):
|
||||
# GSTACK_STATE_DIR — override ~/.gstack state directory
|
||||
# GSTACK_DIR — override auto-detected gstack root
|
||||
#
|
||||
# NOTE: Uses set -uo pipefail (no -e) — telemetry must never exit non-zero
|
||||
set -uo pipefail
|
||||
|
||||
GSTACK_DIR="${GSTACK_DIR:-$(cd "$(dirname "$0")/.." && pwd)}"
|
||||
STATE_DIR="${GSTACK_STATE_DIR:-$HOME/.gstack}"
|
||||
ANALYTICS_DIR="$STATE_DIR/analytics"
|
||||
JSONL_FILE="$ANALYTICS_DIR/skill-usage.jsonl"
|
||||
PENDING_DIR="$ANALYTICS_DIR" # .pending-* files live here
|
||||
CONFIG_CMD="$GSTACK_DIR/bin/gstack-config"
|
||||
VERSION_FILE="$GSTACK_DIR/VERSION"
|
||||
|
||||
# ─── Parse flags ─────────────────────────────────────────────
|
||||
SKILL=""
|
||||
DURATION=""
|
||||
OUTCOME="unknown"
|
||||
USED_BROWSE="false"
|
||||
SESSION_ID=""
|
||||
ERROR_CLASS=""
|
||||
EVENT_TYPE="skill_run"
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--skill) SKILL="$2"; shift 2 ;;
|
||||
--duration) DURATION="$2"; shift 2 ;;
|
||||
--outcome) OUTCOME="$2"; shift 2 ;;
|
||||
--used-browse) USED_BROWSE="$2"; shift 2 ;;
|
||||
--session-id) SESSION_ID="$2"; shift 2 ;;
|
||||
--error-class) ERROR_CLASS="$2"; shift 2 ;;
|
||||
--event-type) EVENT_TYPE="$2"; shift 2 ;;
|
||||
*) shift ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# ─── Read telemetry tier ─────────────────────────────────────
|
||||
TIER="$("$CONFIG_CMD" get telemetry 2>/dev/null || true)"
|
||||
TIER="${TIER:-off}"
|
||||
|
||||
# Validate tier
|
||||
case "$TIER" in
|
||||
off|anonymous|community) ;;
|
||||
*) TIER="off" ;; # invalid value → default to off
|
||||
esac
|
||||
|
||||
if [ "$TIER" = "off" ]; then
|
||||
# Still clear pending markers for this session even if telemetry is off
|
||||
[ -n "$SESSION_ID" ] && rm -f "$PENDING_DIR/.pending-$SESSION_ID" 2>/dev/null || true
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# ─── Finalize stale .pending markers ────────────────────────
|
||||
# Each session gets its own .pending-$SESSION_ID file to avoid races
|
||||
# between concurrent sessions. Finalize any that don't match our session.
|
||||
for PFILE in "$PENDING_DIR"/.pending-*; do
|
||||
[ -f "$PFILE" ] || continue
|
||||
# Skip our own session's marker (it's still in-flight)
|
||||
PFILE_BASE="$(basename "$PFILE")"
|
||||
PFILE_SID="${PFILE_BASE#.pending-}"
|
||||
[ "$PFILE_SID" = "$SESSION_ID" ] && continue
|
||||
|
||||
PENDING_DATA="$(cat "$PFILE" 2>/dev/null || true)"
|
||||
rm -f "$PFILE" 2>/dev/null || true
|
||||
if [ -n "$PENDING_DATA" ]; then
|
||||
# Extract fields from pending marker using grep -o + awk
|
||||
P_SKILL="$(echo "$PENDING_DATA" | grep -o '"skill":"[^"]*"' | head -1 | awk -F'"' '{print $4}')"
|
||||
P_TS="$(echo "$PENDING_DATA" | grep -o '"ts":"[^"]*"' | head -1 | awk -F'"' '{print $4}')"
|
||||
P_SID="$(echo "$PENDING_DATA" | grep -o '"session_id":"[^"]*"' | head -1 | awk -F'"' '{print $4}')"
|
||||
P_VER="$(echo "$PENDING_DATA" | grep -o '"gstack_version":"[^"]*"' | head -1 | awk -F'"' '{print $4}')"
|
||||
P_OS="$(uname -s | tr '[:upper:]' '[:lower:]')"
|
||||
P_ARCH="$(uname -m)"
|
||||
|
||||
# Write the stale event as outcome: unknown
|
||||
mkdir -p "$ANALYTICS_DIR"
|
||||
printf '{"v":1,"ts":"%s","event_type":"skill_run","skill":"%s","session_id":"%s","gstack_version":"%s","os":"%s","arch":"%s","duration_s":null,"outcome":"unknown","error_class":null,"used_browse":false,"sessions":1}\n' \
|
||||
"$P_TS" "$P_SKILL" "$P_SID" "$P_VER" "$P_OS" "$P_ARCH" >> "$JSONL_FILE" 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
|
||||
# Clear our own session's pending marker (we're about to log the real event)
|
||||
[ -n "$SESSION_ID" ] && rm -f "$PENDING_DIR/.pending-$SESSION_ID" 2>/dev/null || true
|
||||
|
||||
# ─── Collect metadata ────────────────────────────────────────
|
||||
TS="$(date -u +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -u +%Y-%m-%dT%H:%M:%S 2>/dev/null || echo "")"
|
||||
GSTACK_VERSION="$(cat "$VERSION_FILE" 2>/dev/null | tr -d '[:space:]' || echo "unknown")"
|
||||
OS="$(uname -s | tr '[:upper:]' '[:lower:]')"
|
||||
ARCH="$(uname -m)"
|
||||
SESSIONS="1"
|
||||
if [ -d "$STATE_DIR/sessions" ]; then
|
||||
_SC="$(find "$STATE_DIR/sessions" -mmin -120 -type f 2>/dev/null | wc -l | tr -d ' \n\r\t')"
|
||||
[ -n "$_SC" ] && [ "$_SC" -gt 0 ] 2>/dev/null && SESSIONS="$_SC"
|
||||
fi
|
||||
|
||||
# Generate installation_id for community tier
|
||||
INSTALL_ID=""
|
||||
if [ "$TIER" = "community" ]; then
|
||||
HOST="$(hostname 2>/dev/null || echo "unknown")"
|
||||
USER="$(whoami 2>/dev/null || echo "unknown")"
|
||||
if command -v shasum >/dev/null 2>&1; then
|
||||
INSTALL_ID="$(printf '%s-%s' "$HOST" "$USER" | shasum -a 256 | awk '{print $1}')"
|
||||
elif command -v sha256sum >/dev/null 2>&1; then
|
||||
INSTALL_ID="$(printf '%s-%s' "$HOST" "$USER" | sha256sum | awk '{print $1}')"
|
||||
elif command -v openssl >/dev/null 2>&1; then
|
||||
INSTALL_ID="$(printf '%s-%s' "$HOST" "$USER" | openssl dgst -sha256 | awk '{print $NF}')"
|
||||
fi
|
||||
# If no SHA-256 command available, install_id stays empty
|
||||
fi
|
||||
|
||||
# Local-only fields (never sent remotely)
|
||||
REPO_SLUG=""
|
||||
BRANCH=""
|
||||
if command -v git >/dev/null 2>&1; then
|
||||
REPO_SLUG="$(git remote get-url origin 2>/dev/null | sed 's|.*[:/]\([^/]*/[^/]*\)\.git$|\1|;s|.*[:/]\([^/]*/[^/]*\)$|\1|' | tr '/' '-' 2>/dev/null || true)"
|
||||
BRANCH="$(git rev-parse --abbrev-ref HEAD 2>/dev/null || true)"
|
||||
fi
|
||||
|
||||
# ─── Construct and append JSON ───────────────────────────────
|
||||
mkdir -p "$ANALYTICS_DIR"
|
||||
|
||||
# Escape null fields
|
||||
ERR_FIELD="null"
|
||||
[ -n "$ERROR_CLASS" ] && ERR_FIELD="\"$ERROR_CLASS\""
|
||||
|
||||
DUR_FIELD="null"
|
||||
[ -n "$DURATION" ] && DUR_FIELD="$DURATION"
|
||||
|
||||
INSTALL_FIELD="null"
|
||||
[ -n "$INSTALL_ID" ] && INSTALL_FIELD="\"$INSTALL_ID\""
|
||||
|
||||
BROWSE_BOOL="false"
|
||||
[ "$USED_BROWSE" = "true" ] && BROWSE_BOOL="true"
|
||||
|
||||
printf '{"v":1,"ts":"%s","event_type":"%s","skill":"%s","session_id":"%s","gstack_version":"%s","os":"%s","arch":"%s","duration_s":%s,"outcome":"%s","error_class":%s,"used_browse":%s,"sessions":%s,"installation_id":%s,"_repo_slug":"%s","_branch":"%s"}\n' \
|
||||
"$TS" "$EVENT_TYPE" "$SKILL" "$SESSION_ID" "$GSTACK_VERSION" "$OS" "$ARCH" \
|
||||
"$DUR_FIELD" "$OUTCOME" "$ERR_FIELD" "$BROWSE_BOOL" "${SESSIONS:-1}" \
|
||||
"$INSTALL_FIELD" "$REPO_SLUG" "$BRANCH" >> "$JSONL_FILE" 2>/dev/null || true
|
||||
|
||||
# ─── Trigger sync if tier is not off ─────────────────────────
|
||||
SYNC_CMD="$GSTACK_DIR/bin/gstack-telemetry-sync"
|
||||
if [ -x "$SYNC_CMD" ]; then
|
||||
"$SYNC_CMD" 2>/dev/null &
|
||||
fi
|
||||
|
||||
exit 0
|
||||
Executable
+127
@@ -0,0 +1,127 @@
|
||||
#!/usr/bin/env bash
|
||||
# gstack-telemetry-sync — sync local JSONL events to Supabase
|
||||
#
|
||||
# Fire-and-forget, backgrounded, rate-limited to once per 5 minutes.
|
||||
# Strips local-only fields before sending. Respects privacy tiers.
|
||||
#
|
||||
# Env overrides (for testing):
|
||||
# GSTACK_STATE_DIR — override ~/.gstack state directory
|
||||
# GSTACK_DIR — override auto-detected gstack root
|
||||
# GSTACK_TELEMETRY_ENDPOINT — override Supabase endpoint URL
|
||||
set -uo pipefail
|
||||
|
||||
GSTACK_DIR="${GSTACK_DIR:-$(cd "$(dirname "$0")/.." && pwd)}"
|
||||
STATE_DIR="${GSTACK_STATE_DIR:-$HOME/.gstack}"
|
||||
ANALYTICS_DIR="$STATE_DIR/analytics"
|
||||
JSONL_FILE="$ANALYTICS_DIR/skill-usage.jsonl"
|
||||
CURSOR_FILE="$ANALYTICS_DIR/.last-sync-line"
|
||||
RATE_FILE="$ANALYTICS_DIR/.last-sync-time"
|
||||
CONFIG_CMD="$GSTACK_DIR/bin/gstack-config"
|
||||
|
||||
# Source Supabase config if not overridden by env
|
||||
if [ -z "${GSTACK_TELEMETRY_ENDPOINT:-}" ] && [ -f "$GSTACK_DIR/supabase/config.sh" ]; then
|
||||
. "$GSTACK_DIR/supabase/config.sh"
|
||||
fi
|
||||
ENDPOINT="${GSTACK_TELEMETRY_ENDPOINT:-}"
|
||||
ANON_KEY="${GSTACK_SUPABASE_ANON_KEY:-}"
|
||||
|
||||
# ─── Pre-checks ──────────────────────────────────────────────
|
||||
# No endpoint configured yet → exit silently
|
||||
[ -z "$ENDPOINT" ] && exit 0
|
||||
|
||||
# No JSONL file → nothing to sync
|
||||
[ -f "$JSONL_FILE" ] || exit 0
|
||||
|
||||
# Rate limit: once per 5 minutes
|
||||
if [ -f "$RATE_FILE" ]; then
|
||||
STALE=$(find "$RATE_FILE" -mmin +5 2>/dev/null || true)
|
||||
[ -z "$STALE" ] && exit 0
|
||||
fi
|
||||
|
||||
# ─── Read tier ───────────────────────────────────────────────
|
||||
TIER="$("$CONFIG_CMD" get telemetry 2>/dev/null || true)"
|
||||
TIER="${TIER:-off}"
|
||||
[ "$TIER" = "off" ] && exit 0
|
||||
|
||||
# ─── Read cursor ─────────────────────────────────────────────
|
||||
CURSOR=0
|
||||
if [ -f "$CURSOR_FILE" ]; then
|
||||
CURSOR="$(cat "$CURSOR_FILE" 2>/dev/null | tr -d ' \n\r\t')"
|
||||
# Validate: must be a non-negative integer
|
||||
case "$CURSOR" in *[!0-9]*) CURSOR=0 ;; esac
|
||||
fi
|
||||
|
||||
# Safety: if cursor exceeds file length, reset
|
||||
TOTAL_LINES="$(wc -l < "$JSONL_FILE" | tr -d ' \n\r\t')"
|
||||
if [ "$CURSOR" -gt "$TOTAL_LINES" ] 2>/dev/null; then
|
||||
CURSOR=0
|
||||
fi
|
||||
|
||||
# Nothing new to sync
|
||||
[ "$CURSOR" -ge "$TOTAL_LINES" ] 2>/dev/null && exit 0
|
||||
|
||||
# ─── Read unsent lines ───────────────────────────────────────
|
||||
SKIP=$(( CURSOR + 1 ))
|
||||
UNSENT="$(tail -n "+$SKIP" "$JSONL_FILE" 2>/dev/null || true)"
|
||||
[ -z "$UNSENT" ] && exit 0
|
||||
|
||||
# ─── Strip local-only fields and build batch ─────────────────
|
||||
BATCH="["
|
||||
FIRST=true
|
||||
COUNT=0
|
||||
|
||||
while IFS= read -r LINE; do
|
||||
# Skip empty or malformed lines
|
||||
[ -z "$LINE" ] && continue
|
||||
echo "$LINE" | grep -q '^{' || continue
|
||||
|
||||
# Strip local-only fields + map JSONL field names to Postgres column names
|
||||
CLEAN="$(echo "$LINE" | sed \
|
||||
-e 's/,"_repo_slug":"[^"]*"//g' \
|
||||
-e 's/,"_branch":"[^"]*"//g' \
|
||||
-e 's/"v":/"schema_version":/g' \
|
||||
-e 's/"ts":/"event_timestamp":/g' \
|
||||
-e 's/"sessions":/"concurrent_sessions":/g' \
|
||||
-e 's/,"repo":"[^"]*"//g')"
|
||||
|
||||
# If anonymous tier, strip installation_id
|
||||
if [ "$TIER" = "anonymous" ]; then
|
||||
CLEAN="$(echo "$CLEAN" | sed 's/,"installation_id":"[^"]*"//g; s/,"installation_id":null//g')"
|
||||
fi
|
||||
|
||||
if [ "$FIRST" = "true" ]; then
|
||||
FIRST=false
|
||||
else
|
||||
BATCH="$BATCH,"
|
||||
fi
|
||||
BATCH="$BATCH$CLEAN"
|
||||
COUNT=$(( COUNT + 1 ))
|
||||
|
||||
# Batch size limit
|
||||
[ "$COUNT" -ge 100 ] && break
|
||||
done <<< "$UNSENT"
|
||||
|
||||
BATCH="$BATCH]"
|
||||
|
||||
# Nothing to send after filtering
|
||||
[ "$COUNT" -eq 0 ] && exit 0
|
||||
|
||||
# ─── POST to Supabase ────────────────────────────────────────
|
||||
HTTP_CODE="$(curl -s -o /dev/null -w '%{http_code}' --max-time 10 \
|
||||
-X POST "${ENDPOINT}/telemetry_events" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "apikey: ${ANON_KEY}" \
|
||||
-H "Authorization: Bearer ${ANON_KEY}" \
|
||||
-H "Prefer: return=minimal" \
|
||||
-d "$BATCH" 2>/dev/null || echo "000")"
|
||||
|
||||
# ─── Update cursor on success (2xx) ─────────────────────────
|
||||
case "$HTTP_CODE" in
|
||||
2*) NEW_CURSOR=$(( CURSOR + COUNT ))
|
||||
echo "$NEW_CURSOR" > "$CURSOR_FILE" 2>/dev/null || true ;;
|
||||
esac
|
||||
|
||||
# Update rate limit marker
|
||||
touch "$RATE_FILE" 2>/dev/null || true
|
||||
|
||||
exit 0
|
||||
@@ -140,6 +140,30 @@ fi
|
||||
# ─── Step 4: Slow path — fetch remote version ────────────────
|
||||
mkdir -p "$STATE_DIR"
|
||||
|
||||
# Fire Supabase install ping in background (parallel, non-blocking)
|
||||
# This logs an update check event for community health metrics.
|
||||
# If the endpoint isn't configured or Supabase is down, this is a no-op.
|
||||
# Source Supabase config for install ping
|
||||
if [ -z "${GSTACK_TELEMETRY_ENDPOINT:-}" ] && [ -f "$GSTACK_DIR/supabase/config.sh" ]; then
|
||||
. "$GSTACK_DIR/supabase/config.sh"
|
||||
fi
|
||||
_SUPA_ENDPOINT="${GSTACK_TELEMETRY_ENDPOINT:-}"
|
||||
_SUPA_KEY="${GSTACK_SUPABASE_ANON_KEY:-}"
|
||||
# Respect telemetry opt-out — don't ping Supabase if user set telemetry: off
|
||||
_TEL_TIER="$("$GSTACK_DIR/bin/gstack-config" get telemetry 2>/dev/null || true)"
|
||||
if [ -n "$_SUPA_ENDPOINT" ] && [ -n "$_SUPA_KEY" ] && [ "${_TEL_TIER:-off}" != "off" ]; then
|
||||
_OS="$(uname -s | tr '[:upper:]' '[:lower:]')"
|
||||
curl -sf --max-time 5 \
|
||||
-X POST "${_SUPA_ENDPOINT}/update_checks" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "apikey: ${_SUPA_KEY}" \
|
||||
-H "Authorization: Bearer ${_SUPA_KEY}" \
|
||||
-H "Prefer: return=minimal" \
|
||||
-d "{\"gstack_version\":\"$LOCAL\",\"os\":\"$_OS\"}" \
|
||||
>/dev/null 2>&1 &
|
||||
fi
|
||||
|
||||
# GitHub raw fetch (primary, always reliable)
|
||||
REMOTE=""
|
||||
REMOTE="$(curl -sf --max-time 5 "$REMOTE_URL" 2>/dev/null || true)"
|
||||
REMOTE="$(echo "$REMOTE" | tr -d '[:space:]')"
|
||||
@@ -161,4 +185,12 @@ echo "UPGRADE_AVAILABLE $LOCAL $REMOTE" > "$CACHE_FILE"
|
||||
if check_snooze "$REMOTE"; then
|
||||
exit 0 # snoozed — stay quiet
|
||||
fi
|
||||
|
||||
# Log upgrade_prompted event (only on slow-path fetch, not cached replays)
|
||||
TEL_CMD="$GSTACK_DIR/bin/gstack-telemetry-log"
|
||||
if [ -x "$TEL_CMD" ]; then
|
||||
"$TEL_CMD" --event-type upgrade_prompted --skill "" --duration 0 \
|
||||
--outcome success --session-id "update-$$-$(date +%s)" 2>/dev/null &
|
||||
fi
|
||||
|
||||
echo "UPGRADE_AVAILABLE $LOCAL $REMOTE"
|
||||
|
||||
@@ -33,8 +33,15 @@ echo "BRANCH: $_BRANCH"
|
||||
echo "PROACTIVE: $_PROACTIVE"
|
||||
_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"
|
||||
mkdir -p ~/.gstack/analytics
|
||||
echo '{"skill":"browse","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
|
||||
for _PF in ~/.gstack/analytics/.pending-*; do [ -f "$_PF" ] && ~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown --session-id "$_SESSION_ID" 2>/dev/null || true; break; done
|
||||
```
|
||||
|
||||
If `PROACTIVE` is `"false"`, do not proactively suggest gstack skills — only invoke
|
||||
@@ -54,6 +61,27 @@ touch ~/.gstack/.completeness-intro-seen
|
||||
|
||||
Only run `open` if the user says yes. Always run `touch` to mark as seen. This only happens once.
|
||||
|
||||
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: After the lake intro is handled,
|
||||
ask the user about telemetry. Use AskUserQuestion:
|
||||
|
||||
> gstack can share anonymous usage data (which skills you use, how long they take, crash info)
|
||||
> to help improve the project. No code, file paths, or repo names are ever sent.
|
||||
> Change anytime with `gstack-config set telemetry off`.
|
||||
|
||||
Options:
|
||||
- A) Yes, share anonymous data (recommended)
|
||||
- B) No thanks
|
||||
|
||||
If A: run `~/.claude/skills/gstack/bin/gstack-config set telemetry anonymous`
|
||||
If B: run `~/.claude/skills/gstack/bin/gstack-config set telemetry off`
|
||||
|
||||
Always run:
|
||||
```bash
|
||||
touch ~/.gstack/.telemetry-prompted
|
||||
```
|
||||
|
||||
This only happens once. If `TEL_PROMPTED` is `yes`, skip this entirely.
|
||||
|
||||
## AskUserQuestion Format
|
||||
|
||||
**ALWAYS follow this structure for every AskUserQuestion call:**
|
||||
@@ -153,6 +181,27 @@ ATTEMPTED: [what you tried]
|
||||
RECOMMENDATION: [what the user should do next]
|
||||
```
|
||||
|
||||
## Telemetry (run last)
|
||||
|
||||
After the skill workflow completes (success, error, or abort), log the telemetry event.
|
||||
Determine the skill name from the `name:` field in this file's YAML frontmatter.
|
||||
Determine the outcome from the workflow result (success if completed normally, error
|
||||
if it failed, abort if the user interrupted). Run this bash:
|
||||
|
||||
```bash
|
||||
_TEL_END=$(date +%s)
|
||||
_TEL_DUR=$(( _TEL_END - _TEL_START ))
|
||||
rm -f ~/.gstack/analytics/.pending-"$_SESSION_ID" 2>/dev/null || true
|
||||
~/.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 &
|
||||
```
|
||||
|
||||
Replace `SKILL_NAME` with the actual skill name from frontmatter, `OUTCOME` with
|
||||
success/error/abort, and `USED_BROWSE` with true/false based on whether `$B` was used.
|
||||
If you cannot determine the outcome, use "unknown". This runs in the background and
|
||||
never blocks the user.
|
||||
|
||||
# browse: QA Testing & Dogfooding
|
||||
|
||||
Persistent headless Chromium. First call auto-starts (~3s), then ~100ms per command.
|
||||
|
||||
@@ -34,8 +34,15 @@ echo "BRANCH: $_BRANCH"
|
||||
echo "PROACTIVE: $_PROACTIVE"
|
||||
_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"
|
||||
mkdir -p ~/.gstack/analytics
|
||||
echo '{"skill":"codex","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
|
||||
for _PF in ~/.gstack/analytics/.pending-*; do [ -f "$_PF" ] && ~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown --session-id "$_SESSION_ID" 2>/dev/null || true; break; done
|
||||
```
|
||||
|
||||
If `PROACTIVE` is `"false"`, do not proactively suggest gstack skills — only invoke
|
||||
@@ -55,6 +62,27 @@ touch ~/.gstack/.completeness-intro-seen
|
||||
|
||||
Only run `open` if the user says yes. Always run `touch` to mark as seen. This only happens once.
|
||||
|
||||
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: After the lake intro is handled,
|
||||
ask the user about telemetry. Use AskUserQuestion:
|
||||
|
||||
> gstack can share anonymous usage data (which skills you use, how long they take, crash info)
|
||||
> to help improve the project. No code, file paths, or repo names are ever sent.
|
||||
> Change anytime with `gstack-config set telemetry off`.
|
||||
|
||||
Options:
|
||||
- A) Yes, share anonymous data (recommended)
|
||||
- B) No thanks
|
||||
|
||||
If A: run `~/.claude/skills/gstack/bin/gstack-config set telemetry anonymous`
|
||||
If B: run `~/.claude/skills/gstack/bin/gstack-config set telemetry off`
|
||||
|
||||
Always run:
|
||||
```bash
|
||||
touch ~/.gstack/.telemetry-prompted
|
||||
```
|
||||
|
||||
This only happens once. If `TEL_PROMPTED` is `yes`, skip this entirely.
|
||||
|
||||
## AskUserQuestion Format
|
||||
|
||||
**ALWAYS follow this structure for every AskUserQuestion call:**
|
||||
@@ -154,6 +182,27 @@ ATTEMPTED: [what you tried]
|
||||
RECOMMENDATION: [what the user should do next]
|
||||
```
|
||||
|
||||
## Telemetry (run last)
|
||||
|
||||
After the skill workflow completes (success, error, or abort), log the telemetry event.
|
||||
Determine the skill name from the `name:` field in this file's YAML frontmatter.
|
||||
Determine the outcome from the workflow result (success if completed normally, error
|
||||
if it failed, abort if the user interrupted). Run this bash:
|
||||
|
||||
```bash
|
||||
_TEL_END=$(date +%s)
|
||||
_TEL_DUR=$(( _TEL_END - _TEL_START ))
|
||||
rm -f ~/.gstack/analytics/.pending-"$_SESSION_ID" 2>/dev/null || true
|
||||
~/.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 &
|
||||
```
|
||||
|
||||
Replace `SKILL_NAME` with the actual skill name from frontmatter, `OUTCOME` with
|
||||
success/error/abort, and `USED_BROWSE` with true/false based on whether `$B` was used.
|
||||
If you cannot determine the outcome, use "unknown". This runs in the background and
|
||||
never blocks the user.
|
||||
|
||||
## Step 0: Detect base branch
|
||||
|
||||
Determine which branch this PR targets. Use the result as "the base branch" in all subsequent steps.
|
||||
|
||||
@@ -38,8 +38,15 @@ echo "BRANCH: $_BRANCH"
|
||||
echo "PROACTIVE: $_PROACTIVE"
|
||||
_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"
|
||||
mkdir -p ~/.gstack/analytics
|
||||
echo '{"skill":"design-consultation","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
|
||||
for _PF in ~/.gstack/analytics/.pending-*; do [ -f "$_PF" ] && ~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown --session-id "$_SESSION_ID" 2>/dev/null || true; break; done
|
||||
```
|
||||
|
||||
If `PROACTIVE` is `"false"`, do not proactively suggest gstack skills — only invoke
|
||||
@@ -59,6 +66,27 @@ touch ~/.gstack/.completeness-intro-seen
|
||||
|
||||
Only run `open` if the user says yes. Always run `touch` to mark as seen. This only happens once.
|
||||
|
||||
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: After the lake intro is handled,
|
||||
ask the user about telemetry. Use AskUserQuestion:
|
||||
|
||||
> gstack can share anonymous usage data (which skills you use, how long they take, crash info)
|
||||
> to help improve the project. No code, file paths, or repo names are ever sent.
|
||||
> Change anytime with `gstack-config set telemetry off`.
|
||||
|
||||
Options:
|
||||
- A) Yes, share anonymous data (recommended)
|
||||
- B) No thanks
|
||||
|
||||
If A: run `~/.claude/skills/gstack/bin/gstack-config set telemetry anonymous`
|
||||
If B: run `~/.claude/skills/gstack/bin/gstack-config set telemetry off`
|
||||
|
||||
Always run:
|
||||
```bash
|
||||
touch ~/.gstack/.telemetry-prompted
|
||||
```
|
||||
|
||||
This only happens once. If `TEL_PROMPTED` is `yes`, skip this entirely.
|
||||
|
||||
## AskUserQuestion Format
|
||||
|
||||
**ALWAYS follow this structure for every AskUserQuestion call:**
|
||||
@@ -158,6 +186,27 @@ ATTEMPTED: [what you tried]
|
||||
RECOMMENDATION: [what the user should do next]
|
||||
```
|
||||
|
||||
## Telemetry (run last)
|
||||
|
||||
After the skill workflow completes (success, error, or abort), log the telemetry event.
|
||||
Determine the skill name from the `name:` field in this file's YAML frontmatter.
|
||||
Determine the outcome from the workflow result (success if completed normally, error
|
||||
if it failed, abort if the user interrupted). Run this bash:
|
||||
|
||||
```bash
|
||||
_TEL_END=$(date +%s)
|
||||
_TEL_DUR=$(( _TEL_END - _TEL_START ))
|
||||
rm -f ~/.gstack/analytics/.pending-"$_SESSION_ID" 2>/dev/null || true
|
||||
~/.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 &
|
||||
```
|
||||
|
||||
Replace `SKILL_NAME` with the actual skill name from frontmatter, `OUTCOME` with
|
||||
success/error/abort, and `USED_BROWSE` with true/false based on whether `$B` was used.
|
||||
If you cannot determine the outcome, use "unknown". This runs in the background and
|
||||
never blocks the user.
|
||||
|
||||
# /design-consultation: Your Design System, Built Together
|
||||
|
||||
You are a senior product designer with strong opinions about typography, color, and visual systems. You don't present menus — you listen, think, research, and propose. You're opinionated but not dogmatic. You explain your reasoning and welcome pushback.
|
||||
|
||||
@@ -38,8 +38,15 @@ echo "BRANCH: $_BRANCH"
|
||||
echo "PROACTIVE: $_PROACTIVE"
|
||||
_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"
|
||||
mkdir -p ~/.gstack/analytics
|
||||
echo '{"skill":"design-review","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
|
||||
for _PF in ~/.gstack/analytics/.pending-*; do [ -f "$_PF" ] && ~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown --session-id "$_SESSION_ID" 2>/dev/null || true; break; done
|
||||
```
|
||||
|
||||
If `PROACTIVE` is `"false"`, do not proactively suggest gstack skills — only invoke
|
||||
@@ -59,6 +66,27 @@ touch ~/.gstack/.completeness-intro-seen
|
||||
|
||||
Only run `open` if the user says yes. Always run `touch` to mark as seen. This only happens once.
|
||||
|
||||
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: After the lake intro is handled,
|
||||
ask the user about telemetry. Use AskUserQuestion:
|
||||
|
||||
> gstack can share anonymous usage data (which skills you use, how long they take, crash info)
|
||||
> to help improve the project. No code, file paths, or repo names are ever sent.
|
||||
> Change anytime with `gstack-config set telemetry off`.
|
||||
|
||||
Options:
|
||||
- A) Yes, share anonymous data (recommended)
|
||||
- B) No thanks
|
||||
|
||||
If A: run `~/.claude/skills/gstack/bin/gstack-config set telemetry anonymous`
|
||||
If B: run `~/.claude/skills/gstack/bin/gstack-config set telemetry off`
|
||||
|
||||
Always run:
|
||||
```bash
|
||||
touch ~/.gstack/.telemetry-prompted
|
||||
```
|
||||
|
||||
This only happens once. If `TEL_PROMPTED` is `yes`, skip this entirely.
|
||||
|
||||
## AskUserQuestion Format
|
||||
|
||||
**ALWAYS follow this structure for every AskUserQuestion call:**
|
||||
@@ -158,6 +186,27 @@ ATTEMPTED: [what you tried]
|
||||
RECOMMENDATION: [what the user should do next]
|
||||
```
|
||||
|
||||
## Telemetry (run last)
|
||||
|
||||
After the skill workflow completes (success, error, or abort), log the telemetry event.
|
||||
Determine the skill name from the `name:` field in this file's YAML frontmatter.
|
||||
Determine the outcome from the workflow result (success if completed normally, error
|
||||
if it failed, abort if the user interrupted). Run this bash:
|
||||
|
||||
```bash
|
||||
_TEL_END=$(date +%s)
|
||||
_TEL_DUR=$(( _TEL_END - _TEL_START ))
|
||||
rm -f ~/.gstack/analytics/.pending-"$_SESSION_ID" 2>/dev/null || true
|
||||
~/.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 &
|
||||
```
|
||||
|
||||
Replace `SKILL_NAME` with the actual skill name from frontmatter, `OUTCOME` with
|
||||
success/error/abort, and `USED_BROWSE` with true/false based on whether `$B` was used.
|
||||
If you cannot determine the outcome, use "unknown". This runs in the background and
|
||||
never blocks the user.
|
||||
|
||||
# /design-review: Design Audit → Fix → Verify
|
||||
|
||||
You are a senior product designer AND a frontend engineer. Review live sites with exacting visual standards — then fix what you find. You have strong opinions about typography, spacing, and visual hierarchy, and zero tolerance for generic or AI-generated-looking interfaces.
|
||||
|
||||
@@ -35,8 +35,15 @@ echo "BRANCH: $_BRANCH"
|
||||
echo "PROACTIVE: $_PROACTIVE"
|
||||
_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"
|
||||
mkdir -p ~/.gstack/analytics
|
||||
echo '{"skill":"document-release","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
|
||||
for _PF in ~/.gstack/analytics/.pending-*; do [ -f "$_PF" ] && ~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown --session-id "$_SESSION_ID" 2>/dev/null || true; break; done
|
||||
```
|
||||
|
||||
If `PROACTIVE` is `"false"`, do not proactively suggest gstack skills — only invoke
|
||||
@@ -56,6 +63,27 @@ touch ~/.gstack/.completeness-intro-seen
|
||||
|
||||
Only run `open` if the user says yes. Always run `touch` to mark as seen. This only happens once.
|
||||
|
||||
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: After the lake intro is handled,
|
||||
ask the user about telemetry. Use AskUserQuestion:
|
||||
|
||||
> gstack can share anonymous usage data (which skills you use, how long they take, crash info)
|
||||
> to help improve the project. No code, file paths, or repo names are ever sent.
|
||||
> Change anytime with `gstack-config set telemetry off`.
|
||||
|
||||
Options:
|
||||
- A) Yes, share anonymous data (recommended)
|
||||
- B) No thanks
|
||||
|
||||
If A: run `~/.claude/skills/gstack/bin/gstack-config set telemetry anonymous`
|
||||
If B: run `~/.claude/skills/gstack/bin/gstack-config set telemetry off`
|
||||
|
||||
Always run:
|
||||
```bash
|
||||
touch ~/.gstack/.telemetry-prompted
|
||||
```
|
||||
|
||||
This only happens once. If `TEL_PROMPTED` is `yes`, skip this entirely.
|
||||
|
||||
## AskUserQuestion Format
|
||||
|
||||
**ALWAYS follow this structure for every AskUserQuestion call:**
|
||||
@@ -155,6 +183,27 @@ ATTEMPTED: [what you tried]
|
||||
RECOMMENDATION: [what the user should do next]
|
||||
```
|
||||
|
||||
## Telemetry (run last)
|
||||
|
||||
After the skill workflow completes (success, error, or abort), log the telemetry event.
|
||||
Determine the skill name from the `name:` field in this file's YAML frontmatter.
|
||||
Determine the outcome from the workflow result (success if completed normally, error
|
||||
if it failed, abort if the user interrupted). Run this bash:
|
||||
|
||||
```bash
|
||||
_TEL_END=$(date +%s)
|
||||
_TEL_DUR=$(( _TEL_END - _TEL_START ))
|
||||
rm -f ~/.gstack/analytics/.pending-"$_SESSION_ID" 2>/dev/null || true
|
||||
~/.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 &
|
||||
```
|
||||
|
||||
Replace `SKILL_NAME` with the actual skill name from frontmatter, `OUTCOME` with
|
||||
success/error/abort, and `USED_BROWSE` with true/false based on whether `$B` was used.
|
||||
If you cannot determine the outcome, use "unknown". This runs in the background and
|
||||
never blocks the user.
|
||||
|
||||
## Step 0: Detect base branch
|
||||
|
||||
Determine which branch this PR targets. Use the result as "the base branch" in all subsequent steps.
|
||||
|
||||
@@ -48,8 +48,15 @@ echo "BRANCH: $_BRANCH"
|
||||
echo "PROACTIVE: $_PROACTIVE"
|
||||
_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"
|
||||
mkdir -p ~/.gstack/analytics
|
||||
echo '{"skill":"investigate","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
|
||||
for _PF in ~/.gstack/analytics/.pending-*; do [ -f "$_PF" ] && ~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown --session-id "$_SESSION_ID" 2>/dev/null || true; break; done
|
||||
```
|
||||
|
||||
If `PROACTIVE` is `"false"`, do not proactively suggest gstack skills — only invoke
|
||||
@@ -69,6 +76,27 @@ touch ~/.gstack/.completeness-intro-seen
|
||||
|
||||
Only run `open` if the user says yes. Always run `touch` to mark as seen. This only happens once.
|
||||
|
||||
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: After the lake intro is handled,
|
||||
ask the user about telemetry. Use AskUserQuestion:
|
||||
|
||||
> gstack can share anonymous usage data (which skills you use, how long they take, crash info)
|
||||
> to help improve the project. No code, file paths, or repo names are ever sent.
|
||||
> Change anytime with `gstack-config set telemetry off`.
|
||||
|
||||
Options:
|
||||
- A) Yes, share anonymous data (recommended)
|
||||
- B) No thanks
|
||||
|
||||
If A: run `~/.claude/skills/gstack/bin/gstack-config set telemetry anonymous`
|
||||
If B: run `~/.claude/skills/gstack/bin/gstack-config set telemetry off`
|
||||
|
||||
Always run:
|
||||
```bash
|
||||
touch ~/.gstack/.telemetry-prompted
|
||||
```
|
||||
|
||||
This only happens once. If `TEL_PROMPTED` is `yes`, skip this entirely.
|
||||
|
||||
## AskUserQuestion Format
|
||||
|
||||
**ALWAYS follow this structure for every AskUserQuestion call:**
|
||||
@@ -168,6 +196,27 @@ ATTEMPTED: [what you tried]
|
||||
RECOMMENDATION: [what the user should do next]
|
||||
```
|
||||
|
||||
## Telemetry (run last)
|
||||
|
||||
After the skill workflow completes (success, error, or abort), log the telemetry event.
|
||||
Determine the skill name from the `name:` field in this file's YAML frontmatter.
|
||||
Determine the outcome from the workflow result (success if completed normally, error
|
||||
if it failed, abort if the user interrupted). Run this bash:
|
||||
|
||||
```bash
|
||||
_TEL_END=$(date +%s)
|
||||
_TEL_DUR=$(( _TEL_END - _TEL_START ))
|
||||
rm -f ~/.gstack/analytics/.pending-"$_SESSION_ID" 2>/dev/null || true
|
||||
~/.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 &
|
||||
```
|
||||
|
||||
Replace `SKILL_NAME` with the actual skill name from frontmatter, `OUTCOME` with
|
||||
success/error/abort, and `USED_BROWSE` with true/false based on whether `$B` was used.
|
||||
If you cannot determine the outcome, use "unknown". This runs in the background and
|
||||
never blocks the user.
|
||||
|
||||
# Systematic Debugging
|
||||
|
||||
## Iron Law
|
||||
|
||||
@@ -39,8 +39,15 @@ echo "BRANCH: $_BRANCH"
|
||||
echo "PROACTIVE: $_PROACTIVE"
|
||||
_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"
|
||||
mkdir -p ~/.gstack/analytics
|
||||
echo '{"skill":"office-hours","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
|
||||
for _PF in ~/.gstack/analytics/.pending-*; do [ -f "$_PF" ] && ~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown --session-id "$_SESSION_ID" 2>/dev/null || true; break; done
|
||||
```
|
||||
|
||||
If `PROACTIVE` is `"false"`, do not proactively suggest gstack skills — only invoke
|
||||
@@ -60,6 +67,27 @@ touch ~/.gstack/.completeness-intro-seen
|
||||
|
||||
Only run `open` if the user says yes. Always run `touch` to mark as seen. This only happens once.
|
||||
|
||||
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: After the lake intro is handled,
|
||||
ask the user about telemetry. Use AskUserQuestion:
|
||||
|
||||
> gstack can share anonymous usage data (which skills you use, how long they take, crash info)
|
||||
> to help improve the project. No code, file paths, or repo names are ever sent.
|
||||
> Change anytime with `gstack-config set telemetry off`.
|
||||
|
||||
Options:
|
||||
- A) Yes, share anonymous data (recommended)
|
||||
- B) No thanks
|
||||
|
||||
If A: run `~/.claude/skills/gstack/bin/gstack-config set telemetry anonymous`
|
||||
If B: run `~/.claude/skills/gstack/bin/gstack-config set telemetry off`
|
||||
|
||||
Always run:
|
||||
```bash
|
||||
touch ~/.gstack/.telemetry-prompted
|
||||
```
|
||||
|
||||
This only happens once. If `TEL_PROMPTED` is `yes`, skip this entirely.
|
||||
|
||||
## AskUserQuestion Format
|
||||
|
||||
**ALWAYS follow this structure for every AskUserQuestion call:**
|
||||
@@ -159,6 +187,27 @@ ATTEMPTED: [what you tried]
|
||||
RECOMMENDATION: [what the user should do next]
|
||||
```
|
||||
|
||||
## Telemetry (run last)
|
||||
|
||||
After the skill workflow completes (success, error, or abort), log the telemetry event.
|
||||
Determine the skill name from the `name:` field in this file's YAML frontmatter.
|
||||
Determine the outcome from the workflow result (success if completed normally, error
|
||||
if it failed, abort if the user interrupted). Run this bash:
|
||||
|
||||
```bash
|
||||
_TEL_END=$(date +%s)
|
||||
_TEL_DUR=$(( _TEL_END - _TEL_START ))
|
||||
rm -f ~/.gstack/analytics/.pending-"$_SESSION_ID" 2>/dev/null || true
|
||||
~/.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 &
|
||||
```
|
||||
|
||||
Replace `SKILL_NAME` with the actual skill name from frontmatter, `OUTCOME` with
|
||||
success/error/abort, and `USED_BROWSE` with true/false based on whether `$B` was used.
|
||||
If you cannot determine the outcome, use "unknown". This runs in the background and
|
||||
never blocks the user.
|
||||
|
||||
# YC Office Hours
|
||||
|
||||
You are a **YC office hours partner**. Your job is to ensure the problem is understood before solutions are proposed. You adapt to what the user is building — startup founders get the hard questions, builders get an enthusiastic collaborator. This skill produces design docs, not code.
|
||||
|
||||
@@ -36,8 +36,15 @@ echo "BRANCH: $_BRANCH"
|
||||
echo "PROACTIVE: $_PROACTIVE"
|
||||
_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"
|
||||
mkdir -p ~/.gstack/analytics
|
||||
echo '{"skill":"plan-ceo-review","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
|
||||
for _PF in ~/.gstack/analytics/.pending-*; do [ -f "$_PF" ] && ~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown --session-id "$_SESSION_ID" 2>/dev/null || true; break; done
|
||||
```
|
||||
|
||||
If `PROACTIVE` is `"false"`, do not proactively suggest gstack skills — only invoke
|
||||
@@ -57,6 +64,27 @@ touch ~/.gstack/.completeness-intro-seen
|
||||
|
||||
Only run `open` if the user says yes. Always run `touch` to mark as seen. This only happens once.
|
||||
|
||||
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: After the lake intro is handled,
|
||||
ask the user about telemetry. Use AskUserQuestion:
|
||||
|
||||
> gstack can share anonymous usage data (which skills you use, how long they take, crash info)
|
||||
> to help improve the project. No code, file paths, or repo names are ever sent.
|
||||
> Change anytime with `gstack-config set telemetry off`.
|
||||
|
||||
Options:
|
||||
- A) Yes, share anonymous data (recommended)
|
||||
- B) No thanks
|
||||
|
||||
If A: run `~/.claude/skills/gstack/bin/gstack-config set telemetry anonymous`
|
||||
If B: run `~/.claude/skills/gstack/bin/gstack-config set telemetry off`
|
||||
|
||||
Always run:
|
||||
```bash
|
||||
touch ~/.gstack/.telemetry-prompted
|
||||
```
|
||||
|
||||
This only happens once. If `TEL_PROMPTED` is `yes`, skip this entirely.
|
||||
|
||||
## AskUserQuestion Format
|
||||
|
||||
**ALWAYS follow this structure for every AskUserQuestion call:**
|
||||
@@ -156,6 +184,27 @@ ATTEMPTED: [what you tried]
|
||||
RECOMMENDATION: [what the user should do next]
|
||||
```
|
||||
|
||||
## Telemetry (run last)
|
||||
|
||||
After the skill workflow completes (success, error, or abort), log the telemetry event.
|
||||
Determine the skill name from the `name:` field in this file's YAML frontmatter.
|
||||
Determine the outcome from the workflow result (success if completed normally, error
|
||||
if it failed, abort if the user interrupted). Run this bash:
|
||||
|
||||
```bash
|
||||
_TEL_END=$(date +%s)
|
||||
_TEL_DUR=$(( _TEL_END - _TEL_START ))
|
||||
rm -f ~/.gstack/analytics/.pending-"$_SESSION_ID" 2>/dev/null || true
|
||||
~/.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 &
|
||||
```
|
||||
|
||||
Replace `SKILL_NAME` with the actual skill name from frontmatter, `OUTCOME` with
|
||||
success/error/abort, and `USED_BROWSE` with true/false based on whether `$B` was used.
|
||||
If you cannot determine the outcome, use "unknown". This runs in the background and
|
||||
never blocks the user.
|
||||
|
||||
## Step 0: Detect base branch
|
||||
|
||||
Determine which branch this PR targets. Use the result as "the base branch" in all subsequent steps.
|
||||
|
||||
@@ -36,8 +36,15 @@ echo "BRANCH: $_BRANCH"
|
||||
echo "PROACTIVE: $_PROACTIVE"
|
||||
_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"
|
||||
mkdir -p ~/.gstack/analytics
|
||||
echo '{"skill":"plan-design-review","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
|
||||
for _PF in ~/.gstack/analytics/.pending-*; do [ -f "$_PF" ] && ~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown --session-id "$_SESSION_ID" 2>/dev/null || true; break; done
|
||||
```
|
||||
|
||||
If `PROACTIVE` is `"false"`, do not proactively suggest gstack skills — only invoke
|
||||
@@ -57,6 +64,27 @@ touch ~/.gstack/.completeness-intro-seen
|
||||
|
||||
Only run `open` if the user says yes. Always run `touch` to mark as seen. This only happens once.
|
||||
|
||||
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: After the lake intro is handled,
|
||||
ask the user about telemetry. Use AskUserQuestion:
|
||||
|
||||
> gstack can share anonymous usage data (which skills you use, how long they take, crash info)
|
||||
> to help improve the project. No code, file paths, or repo names are ever sent.
|
||||
> Change anytime with `gstack-config set telemetry off`.
|
||||
|
||||
Options:
|
||||
- A) Yes, share anonymous data (recommended)
|
||||
- B) No thanks
|
||||
|
||||
If A: run `~/.claude/skills/gstack/bin/gstack-config set telemetry anonymous`
|
||||
If B: run `~/.claude/skills/gstack/bin/gstack-config set telemetry off`
|
||||
|
||||
Always run:
|
||||
```bash
|
||||
touch ~/.gstack/.telemetry-prompted
|
||||
```
|
||||
|
||||
This only happens once. If `TEL_PROMPTED` is `yes`, skip this entirely.
|
||||
|
||||
## AskUserQuestion Format
|
||||
|
||||
**ALWAYS follow this structure for every AskUserQuestion call:**
|
||||
@@ -156,6 +184,27 @@ ATTEMPTED: [what you tried]
|
||||
RECOMMENDATION: [what the user should do next]
|
||||
```
|
||||
|
||||
## Telemetry (run last)
|
||||
|
||||
After the skill workflow completes (success, error, or abort), log the telemetry event.
|
||||
Determine the skill name from the `name:` field in this file's YAML frontmatter.
|
||||
Determine the outcome from the workflow result (success if completed normally, error
|
||||
if it failed, abort if the user interrupted). Run this bash:
|
||||
|
||||
```bash
|
||||
_TEL_END=$(date +%s)
|
||||
_TEL_DUR=$(( _TEL_END - _TEL_START ))
|
||||
rm -f ~/.gstack/analytics/.pending-"$_SESSION_ID" 2>/dev/null || true
|
||||
~/.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 &
|
||||
```
|
||||
|
||||
Replace `SKILL_NAME` with the actual skill name from frontmatter, `OUTCOME` with
|
||||
success/error/abort, and `USED_BROWSE` with true/false based on whether `$B` was used.
|
||||
If you cannot determine the outcome, use "unknown". This runs in the background and
|
||||
never blocks the user.
|
||||
|
||||
## Step 0: Detect base branch
|
||||
|
||||
Determine which branch this PR targets. Use the result as "the base branch" in all subsequent steps.
|
||||
|
||||
@@ -35,8 +35,15 @@ echo "BRANCH: $_BRANCH"
|
||||
echo "PROACTIVE: $_PROACTIVE"
|
||||
_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"
|
||||
mkdir -p ~/.gstack/analytics
|
||||
echo '{"skill":"plan-eng-review","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
|
||||
for _PF in ~/.gstack/analytics/.pending-*; do [ -f "$_PF" ] && ~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown --session-id "$_SESSION_ID" 2>/dev/null || true; break; done
|
||||
```
|
||||
|
||||
If `PROACTIVE` is `"false"`, do not proactively suggest gstack skills — only invoke
|
||||
@@ -56,6 +63,27 @@ touch ~/.gstack/.completeness-intro-seen
|
||||
|
||||
Only run `open` if the user says yes. Always run `touch` to mark as seen. This only happens once.
|
||||
|
||||
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: After the lake intro is handled,
|
||||
ask the user about telemetry. Use AskUserQuestion:
|
||||
|
||||
> gstack can share anonymous usage data (which skills you use, how long they take, crash info)
|
||||
> to help improve the project. No code, file paths, or repo names are ever sent.
|
||||
> Change anytime with `gstack-config set telemetry off`.
|
||||
|
||||
Options:
|
||||
- A) Yes, share anonymous data (recommended)
|
||||
- B) No thanks
|
||||
|
||||
If A: run `~/.claude/skills/gstack/bin/gstack-config set telemetry anonymous`
|
||||
If B: run `~/.claude/skills/gstack/bin/gstack-config set telemetry off`
|
||||
|
||||
Always run:
|
||||
```bash
|
||||
touch ~/.gstack/.telemetry-prompted
|
||||
```
|
||||
|
||||
This only happens once. If `TEL_PROMPTED` is `yes`, skip this entirely.
|
||||
|
||||
## AskUserQuestion Format
|
||||
|
||||
**ALWAYS follow this structure for every AskUserQuestion call:**
|
||||
@@ -155,6 +183,27 @@ ATTEMPTED: [what you tried]
|
||||
RECOMMENDATION: [what the user should do next]
|
||||
```
|
||||
|
||||
## Telemetry (run last)
|
||||
|
||||
After the skill workflow completes (success, error, or abort), log the telemetry event.
|
||||
Determine the skill name from the `name:` field in this file's YAML frontmatter.
|
||||
Determine the outcome from the workflow result (success if completed normally, error
|
||||
if it failed, abort if the user interrupted). Run this bash:
|
||||
|
||||
```bash
|
||||
_TEL_END=$(date +%s)
|
||||
_TEL_DUR=$(( _TEL_END - _TEL_START ))
|
||||
rm -f ~/.gstack/analytics/.pending-"$_SESSION_ID" 2>/dev/null || true
|
||||
~/.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 &
|
||||
```
|
||||
|
||||
Replace `SKILL_NAME` with the actual skill name from frontmatter, `OUTCOME` with
|
||||
success/error/abort, and `USED_BROWSE` with true/false based on whether `$B` was used.
|
||||
If you cannot determine the outcome, use "unknown". This runs in the background and
|
||||
never blocks the user.
|
||||
|
||||
# Plan Review Mode
|
||||
|
||||
Review this plan thoroughly before making any code changes. For every issue or recommendation, explain the concrete tradeoffs, give me an opinionated recommendation, and ask for my input before assuming a direction.
|
||||
|
||||
@@ -32,8 +32,15 @@ echo "BRANCH: $_BRANCH"
|
||||
echo "PROACTIVE: $_PROACTIVE"
|
||||
_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"
|
||||
mkdir -p ~/.gstack/analytics
|
||||
echo '{"skill":"qa-only","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
|
||||
for _PF in ~/.gstack/analytics/.pending-*; do [ -f "$_PF" ] && ~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown --session-id "$_SESSION_ID" 2>/dev/null || true; break; done
|
||||
```
|
||||
|
||||
If `PROACTIVE` is `"false"`, do not proactively suggest gstack skills — only invoke
|
||||
@@ -53,6 +60,27 @@ touch ~/.gstack/.completeness-intro-seen
|
||||
|
||||
Only run `open` if the user says yes. Always run `touch` to mark as seen. This only happens once.
|
||||
|
||||
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: After the lake intro is handled,
|
||||
ask the user about telemetry. Use AskUserQuestion:
|
||||
|
||||
> gstack can share anonymous usage data (which skills you use, how long they take, crash info)
|
||||
> to help improve the project. No code, file paths, or repo names are ever sent.
|
||||
> Change anytime with `gstack-config set telemetry off`.
|
||||
|
||||
Options:
|
||||
- A) Yes, share anonymous data (recommended)
|
||||
- B) No thanks
|
||||
|
||||
If A: run `~/.claude/skills/gstack/bin/gstack-config set telemetry anonymous`
|
||||
If B: run `~/.claude/skills/gstack/bin/gstack-config set telemetry off`
|
||||
|
||||
Always run:
|
||||
```bash
|
||||
touch ~/.gstack/.telemetry-prompted
|
||||
```
|
||||
|
||||
This only happens once. If `TEL_PROMPTED` is `yes`, skip this entirely.
|
||||
|
||||
## AskUserQuestion Format
|
||||
|
||||
**ALWAYS follow this structure for every AskUserQuestion call:**
|
||||
@@ -152,6 +180,27 @@ ATTEMPTED: [what you tried]
|
||||
RECOMMENDATION: [what the user should do next]
|
||||
```
|
||||
|
||||
## Telemetry (run last)
|
||||
|
||||
After the skill workflow completes (success, error, or abort), log the telemetry event.
|
||||
Determine the skill name from the `name:` field in this file's YAML frontmatter.
|
||||
Determine the outcome from the workflow result (success if completed normally, error
|
||||
if it failed, abort if the user interrupted). Run this bash:
|
||||
|
||||
```bash
|
||||
_TEL_END=$(date +%s)
|
||||
_TEL_DUR=$(( _TEL_END - _TEL_START ))
|
||||
rm -f ~/.gstack/analytics/.pending-"$_SESSION_ID" 2>/dev/null || true
|
||||
~/.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 &
|
||||
```
|
||||
|
||||
Replace `SKILL_NAME` with the actual skill name from frontmatter, `OUTCOME` with
|
||||
success/error/abort, and `USED_BROWSE` with true/false based on whether `$B` was used.
|
||||
If you cannot determine the outcome, use "unknown". This runs in the background and
|
||||
never blocks the user.
|
||||
|
||||
# /qa-only: Report-Only QA Testing
|
||||
|
||||
You are a QA engineer. Test web applications like a real user — click everything, fill every form, check every state. Produce a structured report with evidence. **NEVER fix anything.**
|
||||
|
||||
+49
@@ -39,8 +39,15 @@ echo "BRANCH: $_BRANCH"
|
||||
echo "PROACTIVE: $_PROACTIVE"
|
||||
_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"
|
||||
mkdir -p ~/.gstack/analytics
|
||||
echo '{"skill":"qa","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
|
||||
for _PF in ~/.gstack/analytics/.pending-*; do [ -f "$_PF" ] && ~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown --session-id "$_SESSION_ID" 2>/dev/null || true; break; done
|
||||
```
|
||||
|
||||
If `PROACTIVE` is `"false"`, do not proactively suggest gstack skills — only invoke
|
||||
@@ -60,6 +67,27 @@ touch ~/.gstack/.completeness-intro-seen
|
||||
|
||||
Only run `open` if the user says yes. Always run `touch` to mark as seen. This only happens once.
|
||||
|
||||
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: After the lake intro is handled,
|
||||
ask the user about telemetry. Use AskUserQuestion:
|
||||
|
||||
> gstack can share anonymous usage data (which skills you use, how long they take, crash info)
|
||||
> to help improve the project. No code, file paths, or repo names are ever sent.
|
||||
> Change anytime with `gstack-config set telemetry off`.
|
||||
|
||||
Options:
|
||||
- A) Yes, share anonymous data (recommended)
|
||||
- B) No thanks
|
||||
|
||||
If A: run `~/.claude/skills/gstack/bin/gstack-config set telemetry anonymous`
|
||||
If B: run `~/.claude/skills/gstack/bin/gstack-config set telemetry off`
|
||||
|
||||
Always run:
|
||||
```bash
|
||||
touch ~/.gstack/.telemetry-prompted
|
||||
```
|
||||
|
||||
This only happens once. If `TEL_PROMPTED` is `yes`, skip this entirely.
|
||||
|
||||
## AskUserQuestion Format
|
||||
|
||||
**ALWAYS follow this structure for every AskUserQuestion call:**
|
||||
@@ -159,6 +187,27 @@ ATTEMPTED: [what you tried]
|
||||
RECOMMENDATION: [what the user should do next]
|
||||
```
|
||||
|
||||
## Telemetry (run last)
|
||||
|
||||
After the skill workflow completes (success, error, or abort), log the telemetry event.
|
||||
Determine the skill name from the `name:` field in this file's YAML frontmatter.
|
||||
Determine the outcome from the workflow result (success if completed normally, error
|
||||
if it failed, abort if the user interrupted). Run this bash:
|
||||
|
||||
```bash
|
||||
_TEL_END=$(date +%s)
|
||||
_TEL_DUR=$(( _TEL_END - _TEL_START ))
|
||||
rm -f ~/.gstack/analytics/.pending-"$_SESSION_ID" 2>/dev/null || true
|
||||
~/.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 &
|
||||
```
|
||||
|
||||
Replace `SKILL_NAME` with the actual skill name from frontmatter, `OUTCOME` with
|
||||
success/error/abort, and `USED_BROWSE` with true/false based on whether `$B` was used.
|
||||
If you cannot determine the outcome, use "unknown". This runs in the background and
|
||||
never blocks the user.
|
||||
|
||||
## Step 0: Detect base branch
|
||||
|
||||
Determine which branch this PR targets. Use the result as "the base branch" in all subsequent steps.
|
||||
|
||||
@@ -33,8 +33,15 @@ echo "BRANCH: $_BRANCH"
|
||||
echo "PROACTIVE: $_PROACTIVE"
|
||||
_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"
|
||||
mkdir -p ~/.gstack/analytics
|
||||
echo '{"skill":"retro","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
|
||||
for _PF in ~/.gstack/analytics/.pending-*; do [ -f "$_PF" ] && ~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown --session-id "$_SESSION_ID" 2>/dev/null || true; break; done
|
||||
```
|
||||
|
||||
If `PROACTIVE` is `"false"`, do not proactively suggest gstack skills — only invoke
|
||||
@@ -54,6 +61,27 @@ touch ~/.gstack/.completeness-intro-seen
|
||||
|
||||
Only run `open` if the user says yes. Always run `touch` to mark as seen. This only happens once.
|
||||
|
||||
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: After the lake intro is handled,
|
||||
ask the user about telemetry. Use AskUserQuestion:
|
||||
|
||||
> gstack can share anonymous usage data (which skills you use, how long they take, crash info)
|
||||
> to help improve the project. No code, file paths, or repo names are ever sent.
|
||||
> Change anytime with `gstack-config set telemetry off`.
|
||||
|
||||
Options:
|
||||
- A) Yes, share anonymous data (recommended)
|
||||
- B) No thanks
|
||||
|
||||
If A: run `~/.claude/skills/gstack/bin/gstack-config set telemetry anonymous`
|
||||
If B: run `~/.claude/skills/gstack/bin/gstack-config set telemetry off`
|
||||
|
||||
Always run:
|
||||
```bash
|
||||
touch ~/.gstack/.telemetry-prompted
|
||||
```
|
||||
|
||||
This only happens once. If `TEL_PROMPTED` is `yes`, skip this entirely.
|
||||
|
||||
## AskUserQuestion Format
|
||||
|
||||
**ALWAYS follow this structure for every AskUserQuestion call:**
|
||||
@@ -153,6 +181,27 @@ ATTEMPTED: [what you tried]
|
||||
RECOMMENDATION: [what the user should do next]
|
||||
```
|
||||
|
||||
## Telemetry (run last)
|
||||
|
||||
After the skill workflow completes (success, error, or abort), log the telemetry event.
|
||||
Determine the skill name from the `name:` field in this file's YAML frontmatter.
|
||||
Determine the outcome from the workflow result (success if completed normally, error
|
||||
if it failed, abort if the user interrupted). Run this bash:
|
||||
|
||||
```bash
|
||||
_TEL_END=$(date +%s)
|
||||
_TEL_DUR=$(( _TEL_END - _TEL_START ))
|
||||
rm -f ~/.gstack/analytics/.pending-"$_SESSION_ID" 2>/dev/null || true
|
||||
~/.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 &
|
||||
```
|
||||
|
||||
Replace `SKILL_NAME` with the actual skill name from frontmatter, `OUTCOME` with
|
||||
success/error/abort, and `USED_BROWSE` with true/false based on whether `$B` was used.
|
||||
If you cannot determine the outcome, use "unknown". This runs in the background and
|
||||
never blocks the user.
|
||||
|
||||
## Detect default branch
|
||||
|
||||
Before gathering data, detect the repo's default branch name:
|
||||
@@ -245,6 +294,9 @@ find . -name '*.test.*' -o -name '*.spec.*' -o -name '*_test.*' -o -name '*_spec
|
||||
# 11. Regression test commits in window
|
||||
git log origin/<default> --since="<window>" --oneline --grep="test(qa):" --grep="test(design):" --grep="test: coverage"
|
||||
|
||||
# 12. gstack skill usage telemetry (if available)
|
||||
cat ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true
|
||||
|
||||
# 12. Test files changed in window
|
||||
git log origin/<default> --since="<window>" --format="" --name-only | grep -E '\.(test|spec)\.' | sort -u | wc -l
|
||||
```
|
||||
|
||||
@@ -109,6 +109,9 @@ find . -name '*.test.*' -o -name '*.spec.*' -o -name '*_test.*' -o -name '*_spec
|
||||
# 11. Regression test commits in window
|
||||
git log origin/<default> --since="<window>" --oneline --grep="test(qa):" --grep="test(design):" --grep="test: coverage"
|
||||
|
||||
# 12. gstack skill usage telemetry (if available)
|
||||
cat ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true
|
||||
|
||||
# 12. Test files changed in window
|
||||
git log origin/<default> --since="<window>" --format="" --name-only | grep -E '\.(test|spec)\.' | sort -u | wc -l
|
||||
```
|
||||
|
||||
@@ -34,8 +34,15 @@ echo "BRANCH: $_BRANCH"
|
||||
echo "PROACTIVE: $_PROACTIVE"
|
||||
_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"
|
||||
mkdir -p ~/.gstack/analytics
|
||||
echo '{"skill":"review","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
|
||||
for _PF in ~/.gstack/analytics/.pending-*; do [ -f "$_PF" ] && ~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown --session-id "$_SESSION_ID" 2>/dev/null || true; break; done
|
||||
```
|
||||
|
||||
If `PROACTIVE` is `"false"`, do not proactively suggest gstack skills — only invoke
|
||||
@@ -55,6 +62,27 @@ touch ~/.gstack/.completeness-intro-seen
|
||||
|
||||
Only run `open` if the user says yes. Always run `touch` to mark as seen. This only happens once.
|
||||
|
||||
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: After the lake intro is handled,
|
||||
ask the user about telemetry. Use AskUserQuestion:
|
||||
|
||||
> gstack can share anonymous usage data (which skills you use, how long they take, crash info)
|
||||
> to help improve the project. No code, file paths, or repo names are ever sent.
|
||||
> Change anytime with `gstack-config set telemetry off`.
|
||||
|
||||
Options:
|
||||
- A) Yes, share anonymous data (recommended)
|
||||
- B) No thanks
|
||||
|
||||
If A: run `~/.claude/skills/gstack/bin/gstack-config set telemetry anonymous`
|
||||
If B: run `~/.claude/skills/gstack/bin/gstack-config set telemetry off`
|
||||
|
||||
Always run:
|
||||
```bash
|
||||
touch ~/.gstack/.telemetry-prompted
|
||||
```
|
||||
|
||||
This only happens once. If `TEL_PROMPTED` is `yes`, skip this entirely.
|
||||
|
||||
## AskUserQuestion Format
|
||||
|
||||
**ALWAYS follow this structure for every AskUserQuestion call:**
|
||||
@@ -154,6 +182,27 @@ ATTEMPTED: [what you tried]
|
||||
RECOMMENDATION: [what the user should do next]
|
||||
```
|
||||
|
||||
## Telemetry (run last)
|
||||
|
||||
After the skill workflow completes (success, error, or abort), log the telemetry event.
|
||||
Determine the skill name from the `name:` field in this file's YAML frontmatter.
|
||||
Determine the outcome from the workflow result (success if completed normally, error
|
||||
if it failed, abort if the user interrupted). Run this bash:
|
||||
|
||||
```bash
|
||||
_TEL_END=$(date +%s)
|
||||
_TEL_DUR=$(( _TEL_END - _TEL_START ))
|
||||
rm -f ~/.gstack/analytics/.pending-"$_SESSION_ID" 2>/dev/null || true
|
||||
~/.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 &
|
||||
```
|
||||
|
||||
Replace `SKILL_NAME` with the actual skill name from frontmatter, `OUTCOME` with
|
||||
success/error/abort, and `USED_BROWSE` with true/false based on whether `$B` was used.
|
||||
If you cannot determine the outcome, use "unknown". This runs in the background and
|
||||
never blocks the user.
|
||||
|
||||
## Step 0: Detect base branch
|
||||
|
||||
Determine which branch this PR targets. Use the result as "the base branch" in all subsequent steps.
|
||||
|
||||
@@ -153,8 +153,15 @@ echo "BRANCH: $_BRANCH"
|
||||
echo "PROACTIVE: $_PROACTIVE"
|
||||
_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"
|
||||
mkdir -p ~/.gstack/analytics
|
||||
echo '{"skill":"${ctx.skillName}","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
|
||||
for _PF in ~/.gstack/analytics/.pending-*; do [ -f "$_PF" ] && ${ctx.paths.binDir}/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown --session-id "$_SESSION_ID" 2>/dev/null || true; break; done
|
||||
\`\`\``;
|
||||
}
|
||||
|
||||
@@ -179,6 +186,29 @@ touch ~/.gstack/.completeness-intro-seen
|
||||
Only run \`open\` if the user says yes. Always run \`touch\` to mark as seen. This only happens once.`;
|
||||
}
|
||||
|
||||
function generateTelemetryPrompt(ctx: TemplateContext): string {
|
||||
return `If \`TEL_PROMPTED\` is \`no\` AND \`LAKE_INTRO\` is \`yes\`: After the lake intro is handled,
|
||||
ask the user about telemetry. Use AskUserQuestion:
|
||||
|
||||
> gstack can share anonymous usage data (which skills you use, how long they take, crash info)
|
||||
> to help improve the project. No code, file paths, or repo names are ever sent.
|
||||
> Change anytime with \`gstack-config set telemetry off\`.
|
||||
|
||||
Options:
|
||||
- A) Yes, share anonymous data (recommended)
|
||||
- B) No thanks
|
||||
|
||||
If A: run \`${ctx.paths.binDir}/gstack-config set telemetry anonymous\`
|
||||
If B: run \`${ctx.paths.binDir}/gstack-config set telemetry off\`
|
||||
|
||||
Always run:
|
||||
\`\`\`bash
|
||||
touch ~/.gstack/.telemetry-prompted
|
||||
\`\`\`
|
||||
|
||||
This only happens once. If \`TEL_PROMPTED\` is \`yes\`, skip this entirely.`;
|
||||
}
|
||||
|
||||
function generateAskUserFormat(_ctx: TemplateContext): string {
|
||||
return `## AskUserQuestion Format
|
||||
|
||||
@@ -283,7 +313,28 @@ STATUS: BLOCKED | NEEDS_CONTEXT
|
||||
REASON: [1-2 sentences]
|
||||
ATTEMPTED: [what you tried]
|
||||
RECOMMENDATION: [what the user should do next]
|
||||
\`\`\``;
|
||||
\`\`\`
|
||||
|
||||
## Telemetry (run last)
|
||||
|
||||
After the skill workflow completes (success, error, or abort), log the telemetry event.
|
||||
Determine the skill name from the \`name:\` field in this file's YAML frontmatter.
|
||||
Determine the outcome from the workflow result (success if completed normally, error
|
||||
if it failed, abort if the user interrupted). Run this bash:
|
||||
|
||||
\`\`\`bash
|
||||
_TEL_END=$(date +%s)
|
||||
_TEL_DUR=$(( _TEL_END - _TEL_START ))
|
||||
rm -f ~/.gstack/analytics/.pending-"$_SESSION_ID" 2>/dev/null || true
|
||||
~/.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 &
|
||||
\`\`\`
|
||||
|
||||
Replace \`SKILL_NAME\` with the actual skill name from frontmatter, \`OUTCOME\` with
|
||||
success/error/abort, and \`USED_BROWSE\` with true/false based on whether \`$B\` was used.
|
||||
If you cannot determine the outcome, use "unknown". This runs in the background and
|
||||
never blocks the user.`;
|
||||
}
|
||||
|
||||
function generatePreamble(ctx: TemplateContext): string {
|
||||
@@ -291,6 +342,7 @@ function generatePreamble(ctx: TemplateContext): string {
|
||||
generatePreambleBash(ctx),
|
||||
generateUpgradeCheck(ctx),
|
||||
generateLakeIntro(),
|
||||
generateTelemetryPrompt(ctx),
|
||||
generateAskUserFormat(ctx),
|
||||
generateCompletenessSection(),
|
||||
generateContributorMode(),
|
||||
|
||||
@@ -30,8 +30,15 @@ echo "BRANCH: $_BRANCH"
|
||||
echo "PROACTIVE: $_PROACTIVE"
|
||||
_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"
|
||||
mkdir -p ~/.gstack/analytics
|
||||
echo '{"skill":"setup-browser-cookies","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
|
||||
for _PF in ~/.gstack/analytics/.pending-*; do [ -f "$_PF" ] && ~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown --session-id "$_SESSION_ID" 2>/dev/null || true; break; done
|
||||
```
|
||||
|
||||
If `PROACTIVE` is `"false"`, do not proactively suggest gstack skills — only invoke
|
||||
@@ -51,6 +58,27 @@ touch ~/.gstack/.completeness-intro-seen
|
||||
|
||||
Only run `open` if the user says yes. Always run `touch` to mark as seen. This only happens once.
|
||||
|
||||
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: After the lake intro is handled,
|
||||
ask the user about telemetry. Use AskUserQuestion:
|
||||
|
||||
> gstack can share anonymous usage data (which skills you use, how long they take, crash info)
|
||||
> to help improve the project. No code, file paths, or repo names are ever sent.
|
||||
> Change anytime with `gstack-config set telemetry off`.
|
||||
|
||||
Options:
|
||||
- A) Yes, share anonymous data (recommended)
|
||||
- B) No thanks
|
||||
|
||||
If A: run `~/.claude/skills/gstack/bin/gstack-config set telemetry anonymous`
|
||||
If B: run `~/.claude/skills/gstack/bin/gstack-config set telemetry off`
|
||||
|
||||
Always run:
|
||||
```bash
|
||||
touch ~/.gstack/.telemetry-prompted
|
||||
```
|
||||
|
||||
This only happens once. If `TEL_PROMPTED` is `yes`, skip this entirely.
|
||||
|
||||
## AskUserQuestion Format
|
||||
|
||||
**ALWAYS follow this structure for every AskUserQuestion call:**
|
||||
@@ -150,6 +178,27 @@ ATTEMPTED: [what you tried]
|
||||
RECOMMENDATION: [what the user should do next]
|
||||
```
|
||||
|
||||
## Telemetry (run last)
|
||||
|
||||
After the skill workflow completes (success, error, or abort), log the telemetry event.
|
||||
Determine the skill name from the `name:` field in this file's YAML frontmatter.
|
||||
Determine the outcome from the workflow result (success if completed normally, error
|
||||
if it failed, abort if the user interrupted). Run this bash:
|
||||
|
||||
```bash
|
||||
_TEL_END=$(date +%s)
|
||||
_TEL_DUR=$(( _TEL_END - _TEL_START ))
|
||||
rm -f ~/.gstack/analytics/.pending-"$_SESSION_ID" 2>/dev/null || true
|
||||
~/.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 &
|
||||
```
|
||||
|
||||
Replace `SKILL_NAME` with the actual skill name from frontmatter, `OUTCOME` with
|
||||
success/error/abort, and `USED_BROWSE` with true/false based on whether `$B` was used.
|
||||
If you cannot determine the outcome, use "unknown". This runs in the background and
|
||||
never blocks the user.
|
||||
|
||||
# Setup Browser Cookies
|
||||
|
||||
Import logged-in sessions from your real Chromium browser into the headless browse session.
|
||||
|
||||
@@ -33,8 +33,15 @@ echo "BRANCH: $_BRANCH"
|
||||
echo "PROACTIVE: $_PROACTIVE"
|
||||
_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"
|
||||
mkdir -p ~/.gstack/analytics
|
||||
echo '{"skill":"ship","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
|
||||
for _PF in ~/.gstack/analytics/.pending-*; do [ -f "$_PF" ] && ~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown --session-id "$_SESSION_ID" 2>/dev/null || true; break; done
|
||||
```
|
||||
|
||||
If `PROACTIVE` is `"false"`, do not proactively suggest gstack skills — only invoke
|
||||
@@ -54,6 +61,27 @@ touch ~/.gstack/.completeness-intro-seen
|
||||
|
||||
Only run `open` if the user says yes. Always run `touch` to mark as seen. This only happens once.
|
||||
|
||||
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: After the lake intro is handled,
|
||||
ask the user about telemetry. Use AskUserQuestion:
|
||||
|
||||
> gstack can share anonymous usage data (which skills you use, how long they take, crash info)
|
||||
> to help improve the project. No code, file paths, or repo names are ever sent.
|
||||
> Change anytime with `gstack-config set telemetry off`.
|
||||
|
||||
Options:
|
||||
- A) Yes, share anonymous data (recommended)
|
||||
- B) No thanks
|
||||
|
||||
If A: run `~/.claude/skills/gstack/bin/gstack-config set telemetry anonymous`
|
||||
If B: run `~/.claude/skills/gstack/bin/gstack-config set telemetry off`
|
||||
|
||||
Always run:
|
||||
```bash
|
||||
touch ~/.gstack/.telemetry-prompted
|
||||
```
|
||||
|
||||
This only happens once. If `TEL_PROMPTED` is `yes`, skip this entirely.
|
||||
|
||||
## AskUserQuestion Format
|
||||
|
||||
**ALWAYS follow this structure for every AskUserQuestion call:**
|
||||
@@ -153,6 +181,27 @@ ATTEMPTED: [what you tried]
|
||||
RECOMMENDATION: [what the user should do next]
|
||||
```
|
||||
|
||||
## Telemetry (run last)
|
||||
|
||||
After the skill workflow completes (success, error, or abort), log the telemetry event.
|
||||
Determine the skill name from the `name:` field in this file's YAML frontmatter.
|
||||
Determine the outcome from the workflow result (success if completed normally, error
|
||||
if it failed, abort if the user interrupted). Run this bash:
|
||||
|
||||
```bash
|
||||
_TEL_END=$(date +%s)
|
||||
_TEL_DUR=$(( _TEL_END - _TEL_START ))
|
||||
rm -f ~/.gstack/analytics/.pending-"$_SESSION_ID" 2>/dev/null || true
|
||||
~/.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 &
|
||||
```
|
||||
|
||||
Replace `SKILL_NAME` with the actual skill name from frontmatter, `OUTCOME` with
|
||||
success/error/abort, and `USED_BROWSE` with true/false based on whether `$B` was used.
|
||||
If you cannot determine the outcome, use "unknown". This runs in the background and
|
||||
never blocks the user.
|
||||
|
||||
## Step 0: Detect base branch
|
||||
|
||||
Determine which branch this PR targets. Use the result as "the base branch" in all subsequent steps.
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
#!/usr/bin/env bash
|
||||
# Supabase project config for gstack telemetry
|
||||
# These are PUBLIC keys — safe to commit (like Firebase public config).
|
||||
# RLS policies restrict what the anon/publishable key can do (INSERT only).
|
||||
|
||||
GSTACK_SUPABASE_URL="https://frugpmstpnojnhfyimgv.supabase.co"
|
||||
GSTACK_SUPABASE_ANON_KEY="sb_publishable_tR4i6cyMIrYTE3s6OyHGHw_ppx2p6WK"
|
||||
|
||||
# Telemetry ingest endpoint (Data API)
|
||||
GSTACK_TELEMETRY_ENDPOINT="${GSTACK_SUPABASE_URL}/rest/v1"
|
||||
@@ -0,0 +1,59 @@
|
||||
// gstack community-pulse edge function
|
||||
// Returns weekly active installation count for preamble display.
|
||||
// Cached for 1 hour via Cache-Control header.
|
||||
|
||||
import { createClient } from "https://esm.sh/@supabase/supabase-js@2";
|
||||
|
||||
Deno.serve(async () => {
|
||||
const supabase = createClient(
|
||||
Deno.env.get("SUPABASE_URL") ?? "",
|
||||
Deno.env.get("SUPABASE_SERVICE_ROLE_KEY") ?? ""
|
||||
);
|
||||
|
||||
try {
|
||||
// Count unique update checks in the last 7 days (install base proxy)
|
||||
const weekAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString();
|
||||
const twoWeeksAgo = new Date(Date.now() - 14 * 24 * 60 * 60 * 1000).toISOString();
|
||||
|
||||
// This week's active
|
||||
const { count: thisWeek } = await supabase
|
||||
.from("update_checks")
|
||||
.select("*", { count: "exact", head: true })
|
||||
.gte("checked_at", weekAgo);
|
||||
|
||||
// Last week's active (for change %)
|
||||
const { count: lastWeek } = await supabase
|
||||
.from("update_checks")
|
||||
.select("*", { count: "exact", head: true })
|
||||
.gte("checked_at", twoWeeksAgo)
|
||||
.lt("checked_at", weekAgo);
|
||||
|
||||
const current = thisWeek ?? 0;
|
||||
const previous = lastWeek ?? 0;
|
||||
const changePct = previous > 0
|
||||
? Math.round(((current - previous) / previous) * 100)
|
||||
: 0;
|
||||
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
weekly_active: current,
|
||||
change_pct: changePct,
|
||||
}),
|
||||
{
|
||||
status: 200,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Cache-Control": "public, max-age=3600", // 1 hour cache
|
||||
},
|
||||
}
|
||||
);
|
||||
} catch {
|
||||
return new Response(
|
||||
JSON.stringify({ weekly_active: 0, change_pct: 0 }),
|
||||
{
|
||||
status: 200,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,135 @@
|
||||
// gstack telemetry-ingest edge function
|
||||
// Validates and inserts a batch of telemetry events.
|
||||
// Called by bin/gstack-telemetry-sync.
|
||||
|
||||
import { createClient } from "https://esm.sh/@supabase/supabase-js@2";
|
||||
|
||||
interface TelemetryEvent {
|
||||
v: number;
|
||||
ts: string;
|
||||
event_type: string;
|
||||
skill: string;
|
||||
session_id?: string;
|
||||
gstack_version: string;
|
||||
os: string;
|
||||
arch?: string;
|
||||
duration_s?: number;
|
||||
outcome: string;
|
||||
error_class?: string;
|
||||
used_browse?: boolean;
|
||||
sessions?: number;
|
||||
installation_id?: string;
|
||||
}
|
||||
|
||||
const MAX_BATCH_SIZE = 100;
|
||||
const MAX_PAYLOAD_BYTES = 50_000; // 50KB
|
||||
|
||||
Deno.serve(async (req) => {
|
||||
if (req.method !== "POST") {
|
||||
return new Response("POST required", { status: 405 });
|
||||
}
|
||||
|
||||
// Check payload size
|
||||
const contentLength = parseInt(req.headers.get("content-length") || "0");
|
||||
if (contentLength > MAX_PAYLOAD_BYTES) {
|
||||
return new Response("Payload too large", { status: 413 });
|
||||
}
|
||||
|
||||
try {
|
||||
const body = await req.json();
|
||||
const events: TelemetryEvent[] = Array.isArray(body) ? body : [body];
|
||||
|
||||
if (events.length > MAX_BATCH_SIZE) {
|
||||
return new Response(`Batch too large (max ${MAX_BATCH_SIZE})`, { status: 400 });
|
||||
}
|
||||
|
||||
const supabase = createClient(
|
||||
Deno.env.get("SUPABASE_URL") ?? "",
|
||||
Deno.env.get("SUPABASE_SERVICE_ROLE_KEY") ?? ""
|
||||
);
|
||||
|
||||
// Validate and transform events
|
||||
const rows = [];
|
||||
const installationUpserts: Map<string, { version: string; os: string }> = new Map();
|
||||
|
||||
for (const event of events) {
|
||||
// Required fields
|
||||
if (!event.ts || !event.gstack_version || !event.os || !event.outcome) {
|
||||
continue; // skip malformed
|
||||
}
|
||||
|
||||
// Validate schema version
|
||||
if (event.v !== 1) continue;
|
||||
|
||||
// Validate event_type
|
||||
const validTypes = ["skill_run", "upgrade_prompted", "upgrade_completed"];
|
||||
if (!validTypes.includes(event.event_type)) continue;
|
||||
|
||||
rows.push({
|
||||
schema_version: event.v,
|
||||
event_type: event.event_type,
|
||||
gstack_version: String(event.gstack_version).slice(0, 20),
|
||||
os: String(event.os).slice(0, 20),
|
||||
arch: event.arch ? String(event.arch).slice(0, 20) : null,
|
||||
event_timestamp: event.ts,
|
||||
skill: event.skill ? String(event.skill).slice(0, 50) : null,
|
||||
session_id: event.session_id ? String(event.session_id).slice(0, 50) : null,
|
||||
duration_s: typeof event.duration_s === "number" ? event.duration_s : null,
|
||||
outcome: String(event.outcome).slice(0, 20),
|
||||
error_class: event.error_class ? String(event.error_class).slice(0, 100) : null,
|
||||
used_browse: event.used_browse === true,
|
||||
concurrent_sessions: typeof event.sessions === "number" ? event.sessions : 1,
|
||||
installation_id: event.installation_id ? String(event.installation_id).slice(0, 64) : null,
|
||||
});
|
||||
|
||||
// Track installations for upsert
|
||||
if (event.installation_id) {
|
||||
installationUpserts.set(event.installation_id, {
|
||||
version: event.gstack_version,
|
||||
os: event.os,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (rows.length === 0) {
|
||||
return new Response(JSON.stringify({ inserted: 0 }), {
|
||||
status: 200,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
}
|
||||
|
||||
// Insert events
|
||||
const { error: insertError } = await supabase
|
||||
.from("telemetry_events")
|
||||
.insert(rows);
|
||||
|
||||
if (insertError) {
|
||||
return new Response(JSON.stringify({ error: insertError.message }), {
|
||||
status: 500,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
}
|
||||
|
||||
// Upsert installations (update last_seen)
|
||||
for (const [id, data] of installationUpserts) {
|
||||
await supabase
|
||||
.from("installations")
|
||||
.upsert(
|
||||
{
|
||||
installation_id: id,
|
||||
last_seen: new Date().toISOString(),
|
||||
gstack_version: data.version,
|
||||
os: data.os,
|
||||
},
|
||||
{ onConflict: "installation_id" }
|
||||
);
|
||||
}
|
||||
|
||||
return new Response(JSON.stringify({ inserted: rows.length }), {
|
||||
status: 200,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
} catch {
|
||||
return new Response("Invalid request", { status: 400 });
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,37 @@
|
||||
// gstack update-check edge function
|
||||
// Logs an install ping and returns the current latest version.
|
||||
// Called by bin/gstack-update-check as a parallel background request.
|
||||
|
||||
import { createClient } from "https://esm.sh/@supabase/supabase-js@2";
|
||||
|
||||
const CURRENT_VERSION = Deno.env.get("GSTACK_CURRENT_VERSION") || "0.6.4.1";
|
||||
|
||||
Deno.serve(async (req) => {
|
||||
if (req.method !== "POST") {
|
||||
return new Response(CURRENT_VERSION, { status: 200 });
|
||||
}
|
||||
|
||||
try {
|
||||
const { version, os } = await req.json();
|
||||
|
||||
if (!version || !os) {
|
||||
return new Response(CURRENT_VERSION, { status: 200 });
|
||||
}
|
||||
|
||||
const supabase = createClient(
|
||||
Deno.env.get("SUPABASE_URL") ?? "",
|
||||
Deno.env.get("SUPABASE_SERVICE_ROLE_KEY") ?? ""
|
||||
);
|
||||
|
||||
// Log the update check (fire-and-forget)
|
||||
await supabase.from("update_checks").insert({
|
||||
gstack_version: String(version).slice(0, 20),
|
||||
os: String(os).slice(0, 20),
|
||||
});
|
||||
|
||||
return new Response(CURRENT_VERSION, { status: 200 });
|
||||
} catch {
|
||||
// Always return the version, even if logging fails
|
||||
return new Response(CURRENT_VERSION, { status: 200 });
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,89 @@
|
||||
-- gstack telemetry schema
|
||||
-- Tables for tracking usage, installations, and update checks.
|
||||
|
||||
-- Main telemetry events (skill runs, upgrades)
|
||||
CREATE TABLE telemetry_events (
|
||||
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
|
||||
received_at TIMESTAMPTZ DEFAULT now(),
|
||||
schema_version INTEGER NOT NULL DEFAULT 1,
|
||||
event_type TEXT NOT NULL DEFAULT 'skill_run',
|
||||
gstack_version TEXT NOT NULL,
|
||||
os TEXT NOT NULL,
|
||||
arch TEXT,
|
||||
event_timestamp TIMESTAMPTZ NOT NULL,
|
||||
skill TEXT,
|
||||
session_id TEXT,
|
||||
duration_s NUMERIC,
|
||||
outcome TEXT NOT NULL,
|
||||
error_class TEXT,
|
||||
used_browse BOOLEAN DEFAULT false,
|
||||
concurrent_sessions INTEGER DEFAULT 1,
|
||||
installation_id TEXT -- nullable, only for "community" tier
|
||||
);
|
||||
|
||||
-- Index for skill_sequences view performance
|
||||
CREATE INDEX idx_telemetry_session_ts ON telemetry_events (session_id, event_timestamp);
|
||||
-- Index for crash clustering
|
||||
CREATE INDEX idx_telemetry_error ON telemetry_events (error_class, gstack_version) WHERE outcome = 'error';
|
||||
|
||||
-- Retention tracking per installation
|
||||
CREATE TABLE installations (
|
||||
installation_id TEXT PRIMARY KEY,
|
||||
first_seen TIMESTAMPTZ DEFAULT now(),
|
||||
last_seen TIMESTAMPTZ DEFAULT now(),
|
||||
gstack_version TEXT,
|
||||
os TEXT
|
||||
);
|
||||
|
||||
-- Install pings from update checks
|
||||
CREATE TABLE update_checks (
|
||||
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
|
||||
checked_at TIMESTAMPTZ DEFAULT now(),
|
||||
gstack_version TEXT NOT NULL,
|
||||
os TEXT NOT NULL
|
||||
);
|
||||
|
||||
-- RLS: anon key can INSERT and SELECT (all telemetry data is anonymous)
|
||||
ALTER TABLE telemetry_events ENABLE ROW LEVEL SECURITY;
|
||||
CREATE POLICY "anon_insert_only" ON telemetry_events FOR INSERT WITH CHECK (true);
|
||||
CREATE POLICY "anon_select" ON telemetry_events FOR SELECT USING (true);
|
||||
|
||||
ALTER TABLE installations ENABLE ROW LEVEL SECURITY;
|
||||
CREATE POLICY "anon_insert_only" ON installations FOR INSERT WITH CHECK (true);
|
||||
CREATE POLICY "anon_select" ON installations FOR SELECT USING (true);
|
||||
-- Allow upsert (update last_seen)
|
||||
CREATE POLICY "anon_update_last_seen" ON installations FOR UPDATE USING (true) WITH CHECK (true);
|
||||
|
||||
ALTER TABLE update_checks ENABLE ROW LEVEL SECURITY;
|
||||
CREATE POLICY "anon_insert_only" ON update_checks FOR INSERT WITH CHECK (true);
|
||||
CREATE POLICY "anon_select" ON update_checks FOR SELECT USING (true);
|
||||
|
||||
-- Crash clustering view
|
||||
CREATE VIEW crash_clusters AS
|
||||
SELECT
|
||||
error_class,
|
||||
gstack_version,
|
||||
COUNT(*) as total_occurrences,
|
||||
COUNT(DISTINCT installation_id) as identified_users, -- community tier only
|
||||
COUNT(*) - COUNT(installation_id) as anonymous_occurrences, -- events without installation_id
|
||||
MIN(event_timestamp) as first_seen,
|
||||
MAX(event_timestamp) as last_seen
|
||||
FROM telemetry_events
|
||||
WHERE outcome = 'error' AND error_class IS NOT NULL
|
||||
GROUP BY error_class, gstack_version
|
||||
ORDER BY total_occurrences DESC;
|
||||
|
||||
-- Skill sequence co-occurrence view
|
||||
CREATE VIEW skill_sequences AS
|
||||
SELECT
|
||||
a.skill as skill_a,
|
||||
b.skill as skill_b,
|
||||
COUNT(DISTINCT a.session_id) as co_occurrences
|
||||
FROM telemetry_events a
|
||||
JOIN telemetry_events b ON a.session_id = b.session_id
|
||||
AND a.skill != b.skill
|
||||
AND a.event_timestamp < b.event_timestamp
|
||||
WHERE a.event_type = 'skill_run' AND b.event_type = 'skill_run'
|
||||
GROUP BY a.skill, b.skill
|
||||
HAVING COUNT(DISTINCT a.session_id) >= 10
|
||||
ORDER BY co_occurrences DESC;
|
||||
@@ -728,3 +728,50 @@ describe('setup script validation', () => {
|
||||
expect(fnBody).toContain('qa');
|
||||
});
|
||||
});
|
||||
|
||||
describe('telemetry', () => {
|
||||
test('generated SKILL.md contains telemetry start block', () => {
|
||||
const content = fs.readFileSync(path.join(ROOT, 'SKILL.md'), 'utf-8');
|
||||
expect(content).toContain('_TEL_START');
|
||||
expect(content).toContain('_SESSION_ID');
|
||||
expect(content).toContain('TELEMETRY:');
|
||||
expect(content).toContain('TEL_PROMPTED:');
|
||||
expect(content).toContain('gstack-config get telemetry');
|
||||
});
|
||||
|
||||
test('generated SKILL.md contains telemetry opt-in prompt', () => {
|
||||
const content = fs.readFileSync(path.join(ROOT, 'SKILL.md'), 'utf-8');
|
||||
expect(content).toContain('.telemetry-prompted');
|
||||
expect(content).toContain('anonymous usage data');
|
||||
expect(content).toContain('gstack-config set telemetry anonymous');
|
||||
expect(content).toContain('gstack-config set telemetry off');
|
||||
});
|
||||
|
||||
test('generated SKILL.md contains telemetry epilogue', () => {
|
||||
const content = fs.readFileSync(path.join(ROOT, 'SKILL.md'), 'utf-8');
|
||||
expect(content).toContain('Telemetry (run last)');
|
||||
expect(content).toContain('gstack-telemetry-log');
|
||||
expect(content).toContain('_TEL_END');
|
||||
expect(content).toContain('_TEL_DUR');
|
||||
expect(content).toContain('SKILL_NAME');
|
||||
expect(content).toContain('OUTCOME');
|
||||
});
|
||||
|
||||
test('generated SKILL.md contains pending marker handling', () => {
|
||||
const content = fs.readFileSync(path.join(ROOT, 'SKILL.md'), 'utf-8');
|
||||
expect(content).toContain('.pending');
|
||||
expect(content).toContain('_pending_finalize');
|
||||
});
|
||||
|
||||
test('telemetry blocks appear in all skill files that use PREAMBLE', () => {
|
||||
const skills = ['qa', 'ship', 'review', 'plan-ceo-review', 'plan-eng-review', 'retro'];
|
||||
for (const skill of skills) {
|
||||
const skillPath = path.join(ROOT, skill, 'SKILL.md');
|
||||
if (fs.existsSync(skillPath)) {
|
||||
const content = fs.readFileSync(skillPath, 'utf-8');
|
||||
expect(content).toContain('_TEL_START');
|
||||
expect(content).toContain('Telemetry (run last)');
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,278 @@
|
||||
import { describe, test, expect, beforeEach, afterEach } from 'bun:test';
|
||||
import { execSync } from 'child_process';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as os from 'os';
|
||||
|
||||
const ROOT = path.resolve(import.meta.dir, '..');
|
||||
const BIN = path.join(ROOT, 'bin');
|
||||
|
||||
// Each test gets a fresh temp directory for GSTACK_STATE_DIR
|
||||
let tmpDir: string;
|
||||
|
||||
function run(cmd: string, env: Record<string, string> = {}): string {
|
||||
return execSync(cmd, {
|
||||
cwd: ROOT,
|
||||
env: { ...process.env, GSTACK_STATE_DIR: tmpDir, GSTACK_DIR: ROOT, ...env },
|
||||
encoding: 'utf-8',
|
||||
timeout: 10000,
|
||||
}).trim();
|
||||
}
|
||||
|
||||
function setConfig(key: string, value: string) {
|
||||
run(`${BIN}/gstack-config set ${key} ${value}`);
|
||||
}
|
||||
|
||||
function readJsonl(): string[] {
|
||||
const file = path.join(tmpDir, 'analytics', 'skill-usage.jsonl');
|
||||
if (!fs.existsSync(file)) return [];
|
||||
return fs.readFileSync(file, 'utf-8').trim().split('\n').filter(Boolean);
|
||||
}
|
||||
|
||||
function parseJsonl(): any[] {
|
||||
return readJsonl().map(line => JSON.parse(line));
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gstack-tel-'));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fs.rmSync(tmpDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
describe('gstack-telemetry-log', () => {
|
||||
test('appends valid JSONL when tier=anonymous', () => {
|
||||
setConfig('telemetry', 'anonymous');
|
||||
run(`${BIN}/gstack-telemetry-log --skill qa --duration 142 --outcome success --session-id test-123`);
|
||||
|
||||
const events = parseJsonl();
|
||||
expect(events).toHaveLength(1);
|
||||
expect(events[0].v).toBe(1);
|
||||
expect(events[0].skill).toBe('qa');
|
||||
expect(events[0].duration_s).toBe(142);
|
||||
expect(events[0].outcome).toBe('success');
|
||||
expect(events[0].session_id).toBe('test-123');
|
||||
expect(events[0].event_type).toBe('skill_run');
|
||||
expect(events[0].os).toBeTruthy();
|
||||
expect(events[0].gstack_version).toBeTruthy();
|
||||
});
|
||||
|
||||
test('produces no output when tier=off', () => {
|
||||
setConfig('telemetry', 'off');
|
||||
run(`${BIN}/gstack-telemetry-log --skill ship --duration 30 --outcome success --session-id test-456`);
|
||||
|
||||
expect(readJsonl()).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('defaults to off for invalid tier value', () => {
|
||||
setConfig('telemetry', 'invalid_value');
|
||||
run(`${BIN}/gstack-telemetry-log --skill ship --duration 30 --outcome success --session-id test-789`);
|
||||
|
||||
expect(readJsonl()).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('includes installation_id for community tier', () => {
|
||||
setConfig('telemetry', 'community');
|
||||
run(`${BIN}/gstack-telemetry-log --skill review --duration 100 --outcome success --session-id comm-123`);
|
||||
|
||||
const events = parseJsonl();
|
||||
expect(events).toHaveLength(1);
|
||||
// installation_id should be a SHA-256 hash (64 hex chars)
|
||||
expect(events[0].installation_id).toMatch(/^[a-f0-9]{64}$/);
|
||||
});
|
||||
|
||||
test('installation_id is null for anonymous tier', () => {
|
||||
setConfig('telemetry', 'anonymous');
|
||||
run(`${BIN}/gstack-telemetry-log --skill qa --duration 50 --outcome success --session-id anon-123`);
|
||||
|
||||
const events = parseJsonl();
|
||||
expect(events[0].installation_id).toBeNull();
|
||||
});
|
||||
|
||||
test('includes error_class when provided', () => {
|
||||
setConfig('telemetry', 'anonymous');
|
||||
run(`${BIN}/gstack-telemetry-log --skill browse --duration 10 --outcome error --error-class timeout --session-id err-123`);
|
||||
|
||||
const events = parseJsonl();
|
||||
expect(events[0].error_class).toBe('timeout');
|
||||
expect(events[0].outcome).toBe('error');
|
||||
});
|
||||
|
||||
test('handles missing duration gracefully', () => {
|
||||
setConfig('telemetry', 'anonymous');
|
||||
run(`${BIN}/gstack-telemetry-log --skill qa --outcome success --session-id nodur-123`);
|
||||
|
||||
const events = parseJsonl();
|
||||
expect(events[0].duration_s).toBeNull();
|
||||
});
|
||||
|
||||
test('supports event_type flag', () => {
|
||||
setConfig('telemetry', 'anonymous');
|
||||
run(`${BIN}/gstack-telemetry-log --event-type upgrade_prompted --skill "" --outcome success --session-id up-123`);
|
||||
|
||||
const events = parseJsonl();
|
||||
expect(events[0].event_type).toBe('upgrade_prompted');
|
||||
});
|
||||
|
||||
test('includes local-only fields (_repo_slug, _branch)', () => {
|
||||
setConfig('telemetry', 'anonymous');
|
||||
run(`${BIN}/gstack-telemetry-log --skill qa --duration 50 --outcome success --session-id local-123`);
|
||||
|
||||
const events = parseJsonl();
|
||||
// These should be present in local JSONL
|
||||
expect(events[0]).toHaveProperty('_repo_slug');
|
||||
expect(events[0]).toHaveProperty('_branch');
|
||||
});
|
||||
|
||||
test('creates analytics directory if missing', () => {
|
||||
// Remove analytics dir
|
||||
const analyticsDir = path.join(tmpDir, 'analytics');
|
||||
if (fs.existsSync(analyticsDir)) fs.rmSync(analyticsDir, { recursive: true });
|
||||
|
||||
setConfig('telemetry', 'anonymous');
|
||||
run(`${BIN}/gstack-telemetry-log --skill qa --duration 50 --outcome success --session-id mkdir-123`);
|
||||
|
||||
expect(fs.existsSync(analyticsDir)).toBe(true);
|
||||
expect(readJsonl()).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('.pending marker', () => {
|
||||
test('finalizes stale .pending from another session as outcome:unknown', () => {
|
||||
setConfig('telemetry', 'anonymous');
|
||||
|
||||
// Write a fake .pending marker from a different session
|
||||
const analyticsDir = path.join(tmpDir, 'analytics');
|
||||
fs.mkdirSync(analyticsDir, { recursive: true });
|
||||
fs.writeFileSync(
|
||||
path.join(analyticsDir, '.pending-old-123'),
|
||||
'{"skill":"old-skill","ts":"2026-03-18T00:00:00Z","session_id":"old-123","gstack_version":"0.6.4"}'
|
||||
);
|
||||
|
||||
// Run telemetry-log with a DIFFERENT session — should finalize the old pending marker
|
||||
run(`${BIN}/gstack-telemetry-log --skill qa --duration 50 --outcome success --session-id new-456`);
|
||||
|
||||
const events = parseJsonl();
|
||||
expect(events).toHaveLength(2);
|
||||
|
||||
// First event: finalized pending
|
||||
expect(events[0].skill).toBe('old-skill');
|
||||
expect(events[0].outcome).toBe('unknown');
|
||||
expect(events[0].session_id).toBe('old-123');
|
||||
|
||||
// Second event: new event
|
||||
expect(events[1].skill).toBe('qa');
|
||||
expect(events[1].outcome).toBe('success');
|
||||
});
|
||||
|
||||
test('.pending-SESSION file is removed after finalization', () => {
|
||||
setConfig('telemetry', 'anonymous');
|
||||
|
||||
const analyticsDir = path.join(tmpDir, 'analytics');
|
||||
fs.mkdirSync(analyticsDir, { recursive: true });
|
||||
const pendingPath = path.join(analyticsDir, '.pending-stale-session');
|
||||
fs.writeFileSync(pendingPath, '{"skill":"stale","ts":"2026-03-18T00:00:00Z","session_id":"stale-session","gstack_version":"v"}');
|
||||
|
||||
run(`${BIN}/gstack-telemetry-log --skill qa --duration 50 --outcome success --session-id new-456`);
|
||||
|
||||
expect(fs.existsSync(pendingPath)).toBe(false);
|
||||
});
|
||||
|
||||
test('does not finalize own session pending marker', () => {
|
||||
setConfig('telemetry', 'anonymous');
|
||||
|
||||
const analyticsDir = path.join(tmpDir, 'analytics');
|
||||
fs.mkdirSync(analyticsDir, { recursive: true });
|
||||
// Create pending for same session ID we'll use
|
||||
const pendingPath = path.join(analyticsDir, '.pending-same-session');
|
||||
fs.writeFileSync(pendingPath, '{"skill":"in-flight","ts":"2026-03-18T00:00:00Z","session_id":"same-session","gstack_version":"v"}');
|
||||
|
||||
run(`${BIN}/gstack-telemetry-log --skill qa --duration 50 --outcome success --session-id same-session`);
|
||||
|
||||
// Should only have 1 event (the new one), not finalize own pending
|
||||
const events = parseJsonl();
|
||||
expect(events).toHaveLength(1);
|
||||
expect(events[0].skill).toBe('qa');
|
||||
});
|
||||
|
||||
test('tier=off still clears own session pending', () => {
|
||||
setConfig('telemetry', 'off');
|
||||
|
||||
const analyticsDir = path.join(tmpDir, 'analytics');
|
||||
fs.mkdirSync(analyticsDir, { recursive: true });
|
||||
const pendingPath = path.join(analyticsDir, '.pending-off-123');
|
||||
fs.writeFileSync(pendingPath, '{"skill":"stale","ts":"2026-03-18T00:00:00Z","session_id":"off-123","gstack_version":"v"}');
|
||||
|
||||
run(`${BIN}/gstack-telemetry-log --skill qa --duration 50 --outcome success --session-id off-123`);
|
||||
|
||||
expect(fs.existsSync(pendingPath)).toBe(false);
|
||||
// But no JSONL entries since tier=off
|
||||
expect(readJsonl()).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('gstack-analytics', () => {
|
||||
test('shows "no data" for empty JSONL', () => {
|
||||
const output = run(`${BIN}/gstack-analytics`);
|
||||
expect(output).toContain('no data');
|
||||
});
|
||||
|
||||
test('renders usage dashboard with events', () => {
|
||||
setConfig('telemetry', 'anonymous');
|
||||
run(`${BIN}/gstack-telemetry-log --skill qa --duration 120 --outcome success --session-id a-1`);
|
||||
run(`${BIN}/gstack-telemetry-log --skill qa --duration 60 --outcome success --session-id a-2`);
|
||||
run(`${BIN}/gstack-telemetry-log --skill ship --duration 30 --outcome error --error-class timeout --session-id a-3`);
|
||||
|
||||
const output = run(`${BIN}/gstack-analytics all`);
|
||||
expect(output).toContain('/qa');
|
||||
expect(output).toContain('/ship');
|
||||
expect(output).toContain('2 runs');
|
||||
expect(output).toContain('1 runs');
|
||||
expect(output).toContain('Success rate: 66%');
|
||||
expect(output).toContain('Errors: 1');
|
||||
});
|
||||
|
||||
test('filters by time window', () => {
|
||||
setConfig('telemetry', 'anonymous');
|
||||
run(`${BIN}/gstack-telemetry-log --skill qa --duration 60 --outcome success --session-id t-1`);
|
||||
|
||||
const output7d = run(`${BIN}/gstack-analytics 7d`);
|
||||
expect(output7d).toContain('/qa');
|
||||
expect(output7d).toContain('last 7 days');
|
||||
});
|
||||
});
|
||||
|
||||
describe('gstack-telemetry-sync', () => {
|
||||
test('exits silently with no endpoint configured', () => {
|
||||
// Default: GSTACK_TELEMETRY_ENDPOINT is not set → exit 0
|
||||
const result = run(`${BIN}/gstack-telemetry-sync`);
|
||||
expect(result).toBe('');
|
||||
});
|
||||
|
||||
test('exits silently with no JSONL file', () => {
|
||||
const result = run(`${BIN}/gstack-telemetry-sync`, { GSTACK_TELEMETRY_ENDPOINT: 'http://localhost:9999' });
|
||||
expect(result).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('gstack-community-dashboard', () => {
|
||||
test('shows unconfigured message when no Supabase config available', () => {
|
||||
// Use a fake GSTACK_DIR with no supabase/config.sh
|
||||
const output = run(`${BIN}/gstack-community-dashboard`, {
|
||||
GSTACK_DIR: tmpDir,
|
||||
GSTACK_SUPABASE_URL: '',
|
||||
GSTACK_SUPABASE_ANON_KEY: '',
|
||||
});
|
||||
expect(output).toContain('Supabase not configured');
|
||||
expect(output).toContain('gstack-analytics');
|
||||
});
|
||||
|
||||
test('connects to Supabase when config exists', () => {
|
||||
// Use the real GSTACK_DIR which has supabase/config.sh
|
||||
const output = run(`${BIN}/gstack-community-dashboard`);
|
||||
expect(output).toContain('gstack community dashboard');
|
||||
// Should not show "not configured" since config.sh exists
|
||||
expect(output).not.toContain('Supabase not configured');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user