mirror of https://github.com/garrytan/gstack.git
Compare commits
6 Commits
cadd40db5c
...
0dad70b095
| Author | SHA1 | Date |
|---|---|---|
|
|
0dad70b095 | |
|
|
c43c850cae | |
|
|
b617f7916d | |
|
|
c2cb8bc423 | |
|
|
d67e0bd168 | |
|
|
aa8ba4d43c |
38
CHANGELOG.md
38
CHANGELOG.md
|
|
@ -1,5 +1,43 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## [1.55.1.0] - 2026-06-02
|
||||||
|
|
||||||
|
## **Telemetry now tells you exactly what it records and where it stays. The project-slug helper hands the shell a safe identifier on every path.**
|
||||||
|
|
||||||
|
The telemetry opt-in screen now states the truth without asterisks: it shares skill name, duration, crashes, and a stable device ID, with no code and no file paths, and your repo name is recorded locally only and stripped before any upload. Under the hood, the helper that every skill uses to find your project (`gstack-slug`) now filters its output to `[a-zA-Z0-9._-]` on every path, including the cached one, so the value that gets handed to the shell is always a plain identifier. Two regression tests lock both behaviors so they can't quietly drift back.
|
||||||
|
|
||||||
|
### The guarantees that matter
|
||||||
|
|
||||||
|
These are enforced by tests in this release, not promises (`bun test test/telemetry-repo-strip.test.ts test/gstack-slug-sanitize.test.ts`):
|
||||||
|
|
||||||
|
| Guarantee | Pinned by |
|
||||||
|
|-----------|-----------|
|
||||||
|
| Your repo name never leaves the machine (stripped before upload) | `telemetry-repo-strip.test.ts` — floor + producer-coverage + runs the real strip over a sample event |
|
||||||
|
| A tampered slug cache can't put shell characters into the helper's output | `gstack-slug-sanitize.test.ts` — fails if the sanitization is removed |
|
||||||
|
| The consent copy matches what the code actually does | `generate-telemetry-prompt.ts` (regenerated into every skill) |
|
||||||
|
|
||||||
|
The repo-identity test covers all three producer fields (`repo`, `_repo_slug`, `_branch`), so adding a new field that forgets to get stripped fails CI rather than shipping silently.
|
||||||
|
|
||||||
|
### What this means for you
|
||||||
|
|
||||||
|
Your telemetry choice screen now describes what actually happens, so you can opt in (or not) on accurate information. If you share a machine or have ever worried about a tampered `~/.gstack` cache, the slug helper now refuses to pass anything but a safe identifier to the shell. Nothing to do — both land automatically on upgrade.
|
||||||
|
|
||||||
|
### Itemized changes
|
||||||
|
|
||||||
|
#### Changed
|
||||||
|
- Telemetry consent copy is now accurate: "No code or file paths. Your repo name is recorded locally only and stripped before any upload" (was "No code, file paths, or repo names").
|
||||||
|
|
||||||
|
#### Fixed
|
||||||
|
- `gstack-slug` sanitizes its output to `[a-zA-Z0-9._-]` on every path, including values read from its on-disk cache, so `eval "$(gstack-slug)"` always receives a plain identifier. A tampered cache file is also healed on the next write.
|
||||||
|
- The telemetry preamble sanitizes the repo basename before building its JSON line, so an unusual repo directory name can't malform the local analytics record.
|
||||||
|
|
||||||
|
#### Added
|
||||||
|
- `test/telemetry-repo-strip.test.ts` — enforces that no repo/branch identity field reaches the upload batch (floor + producer-coverage + real-strip behavior).
|
||||||
|
- `test/gstack-slug-sanitize.test.ts` — regression test proving a poisoned slug cache cannot inject shell metacharacters.
|
||||||
|
|
||||||
|
#### For contributors
|
||||||
|
- The consent copy and repo-basename handling live in `scripts/resolvers/preamble/`; all `SKILL.md` files and the ship goldens were regenerated from those resolvers.
|
||||||
|
|
||||||
## [1.55.0.0] - 2026-05-30
|
## [1.55.0.0] - 2026-05-30
|
||||||
|
|
||||||
## **`/sync-gbrain` can no longer be the trigger that lets gbrain delete your repo. The headed browser stops crash-looping, and gbrain installs the current release instead of a pin 23 versions stale.**
|
## **`/sync-gbrain` can no longer be the trigger that lets gbrain delete your repo. The headed browser stops crash-looping, and gbrain installs the current release instead of a pin 23 versions stale.**
|
||||||
|
|
|
||||||
4
SKILL.md
4
SKILL.md
|
|
@ -60,7 +60,7 @@ _QUESTION_TUNING=$(~/.claude/skills/gstack/bin/gstack-config get question_tuning
|
||||||
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
||||||
mkdir -p ~/.gstack/analytics
|
mkdir -p ~/.gstack/analytics
|
||||||
if [ "$_TEL" != "off" ]; then
|
if [ "$_TEL" != "off" ]; then
|
||||||
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
|
echo '{"skill":"gstack","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(_repo=$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null | tr -cd 'a-zA-Z0-9._-'); echo "${_repo:-unknown}")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
||||||
if [ -f "$_PF" ]; then
|
if [ -f "$_PF" ]; then
|
||||||
|
|
@ -170,7 +170,7 @@ Only run `open` if yes. Always run `touch`.
|
||||||
|
|
||||||
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
||||||
|
|
||||||
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code, file paths, or repo names.
|
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code or file paths. Your repo name is recorded locally only and stripped before any upload.
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
- A) Help gstack get better! (recommended)
|
- A) Help gstack get better! (recommended)
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,7 @@ _QUESTION_TUNING=$(~/.claude/skills/gstack/bin/gstack-config get question_tuning
|
||||||
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
||||||
mkdir -p ~/.gstack/analytics
|
mkdir -p ~/.gstack/analytics
|
||||||
if [ "$_TEL" != "off" ]; then
|
if [ "$_TEL" != "off" ]; then
|
||||||
echo '{"skill":"autoplan","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
|
echo '{"skill":"autoplan","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(_repo=$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null | tr -cd 'a-zA-Z0-9._-'); echo "${_repo:-unknown}")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
||||||
if [ -f "$_PF" ]; then
|
if [ -f "$_PF" ]; then
|
||||||
|
|
@ -179,7 +179,7 @@ Only run `open` if yes. Always run `touch`.
|
||||||
|
|
||||||
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
||||||
|
|
||||||
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code, file paths, or repo names.
|
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code or file paths. Your repo name is recorded locally only and stripped before any upload.
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
- A) Help gstack get better! (recommended)
|
- A) Help gstack get better! (recommended)
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,7 @@ _QUESTION_TUNING=$(~/.claude/skills/gstack/bin/gstack-config get question_tuning
|
||||||
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
||||||
mkdir -p ~/.gstack/analytics
|
mkdir -p ~/.gstack/analytics
|
||||||
if [ "$_TEL" != "off" ]; then
|
if [ "$_TEL" != "off" ]; then
|
||||||
echo '{"skill":"benchmark-models","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
|
echo '{"skill":"benchmark-models","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(_repo=$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null | tr -cd 'a-zA-Z0-9._-'); echo "${_repo:-unknown}")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
||||||
if [ -f "$_PF" ]; then
|
if [ -f "$_PF" ]; then
|
||||||
|
|
@ -173,7 +173,7 @@ Only run `open` if yes. Always run `touch`.
|
||||||
|
|
||||||
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
||||||
|
|
||||||
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code, file paths, or repo names.
|
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code or file paths. Your repo name is recorded locally only and stripped before any upload.
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
- A) Help gstack get better! (recommended)
|
- A) Help gstack get better! (recommended)
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,7 @@ _QUESTION_TUNING=$(~/.claude/skills/gstack/bin/gstack-config get question_tuning
|
||||||
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
||||||
mkdir -p ~/.gstack/analytics
|
mkdir -p ~/.gstack/analytics
|
||||||
if [ "$_TEL" != "off" ]; then
|
if [ "$_TEL" != "off" ]; then
|
||||||
echo '{"skill":"benchmark","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
|
echo '{"skill":"benchmark","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(_repo=$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null | tr -cd 'a-zA-Z0-9._-'); echo "${_repo:-unknown}")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
||||||
if [ -f "$_PF" ]; then
|
if [ -f "$_PF" ]; then
|
||||||
|
|
@ -173,7 +173,7 @@ Only run `open` if yes. Always run `touch`.
|
||||||
|
|
||||||
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
||||||
|
|
||||||
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code, file paths, or repo names.
|
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code or file paths. Your repo name is recorded locally only and stripped before any upload.
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
- A) Help gstack get better! (recommended)
|
- A) Help gstack get better! (recommended)
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,14 @@ fi
|
||||||
# 3. Fallback to basename only when there's truly no git remote configured
|
# 3. Fallback to basename only when there's truly no git remote configured
|
||||||
SLUG="${SLUG:-$(basename "$PWD" | tr -cd 'a-zA-Z0-9._-')}"
|
SLUG="${SLUG:-$(basename "$PWD" | tr -cd 'a-zA-Z0-9._-')}"
|
||||||
|
|
||||||
|
# 3b. Re-sanitize unconditionally before the value is echoed into `eval`/`source`
|
||||||
|
# output. The compute (2) and fallback (3) paths already filter, but a value
|
||||||
|
# read straight from the cache file (1) does NOT — a poisoned
|
||||||
|
# ~/.gstack/slug-cache/<key> would otherwise inject shell into
|
||||||
|
# `eval "$(gstack-slug)"`. Filtering here honors the [a-zA-Z0-9._-] invariant
|
||||||
|
# promised in the header on every path, and heals a poisoned cache on write (4).
|
||||||
|
SLUG=$(printf '%s' "$SLUG" | tr -cd 'a-zA-Z0-9._-')
|
||||||
|
|
||||||
# 4. Cache the slug for future sessions (atomic write, fail silently)
|
# 4. Cache the slug for future sessions (atomic write, fail silently)
|
||||||
if [[ -n "$SLUG" ]]; then
|
if [[ -n "$SLUG" ]]; then
|
||||||
mkdir -p "$CACHE_DIR" 2>/dev/null || true
|
mkdir -p "$CACHE_DIR" 2>/dev/null || true
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,7 @@ _QUESTION_TUNING=$(~/.claude/skills/gstack/bin/gstack-config get question_tuning
|
||||||
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
||||||
mkdir -p ~/.gstack/analytics
|
mkdir -p ~/.gstack/analytics
|
||||||
if [ "$_TEL" != "off" ]; then
|
if [ "$_TEL" != "off" ]; then
|
||||||
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
|
echo '{"skill":"browse","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(_repo=$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null | tr -cd 'a-zA-Z0-9._-'); echo "${_repo:-unknown}")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
||||||
if [ -f "$_PF" ]; then
|
if [ -f "$_PF" ]; then
|
||||||
|
|
@ -171,7 +171,7 @@ Only run `open` if yes. Always run `touch`.
|
||||||
|
|
||||||
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
||||||
|
|
||||||
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code, file paths, or repo names.
|
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code or file paths. Your repo name is recorded locally only and stripped before any upload.
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
- A) Help gstack get better! (recommended)
|
- A) Help gstack get better! (recommended)
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,7 @@ _QUESTION_TUNING=$(~/.claude/skills/gstack/bin/gstack-config get question_tuning
|
||||||
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
||||||
mkdir -p ~/.gstack/analytics
|
mkdir -p ~/.gstack/analytics
|
||||||
if [ "$_TEL" != "off" ]; then
|
if [ "$_TEL" != "off" ]; then
|
||||||
echo '{"skill":"canary","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
|
echo '{"skill":"canary","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(_repo=$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null | tr -cd 'a-zA-Z0-9._-'); echo "${_repo:-unknown}")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
||||||
if [ -f "$_PF" ]; then
|
if [ -f "$_PF" ]; then
|
||||||
|
|
@ -171,7 +171,7 @@ Only run `open` if yes. Always run `touch`.
|
||||||
|
|
||||||
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
||||||
|
|
||||||
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code, file paths, or repo names.
|
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code or file paths. Your repo name is recorded locally only and stripped before any upload.
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
- A) Help gstack get better! (recommended)
|
- A) Help gstack get better! (recommended)
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,7 @@ _QUESTION_TUNING=$(~/.claude/skills/gstack/bin/gstack-config get question_tuning
|
||||||
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
||||||
mkdir -p ~/.gstack/analytics
|
mkdir -p ~/.gstack/analytics
|
||||||
if [ "$_TEL" != "off" ]; then
|
if [ "$_TEL" != "off" ]; then
|
||||||
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
|
echo '{"skill":"codex","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(_repo=$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null | tr -cd 'a-zA-Z0-9._-'); echo "${_repo:-unknown}")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
||||||
if [ -f "$_PF" ]; then
|
if [ -f "$_PF" ]; then
|
||||||
|
|
@ -174,7 +174,7 @@ Only run `open` if yes. Always run `touch`.
|
||||||
|
|
||||||
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
||||||
|
|
||||||
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code, file paths, or repo names.
|
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code or file paths. Your repo name is recorded locally only and stripped before any upload.
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
- A) Help gstack get better! (recommended)
|
- A) Help gstack get better! (recommended)
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,7 @@ _QUESTION_TUNING=$(~/.claude/skills/gstack/bin/gstack-config get question_tuning
|
||||||
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
||||||
mkdir -p ~/.gstack/analytics
|
mkdir -p ~/.gstack/analytics
|
||||||
if [ "$_TEL" != "off" ]; then
|
if [ "$_TEL" != "off" ]; then
|
||||||
echo '{"skill":"context-restore","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
|
echo '{"skill":"context-restore","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(_repo=$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null | tr -cd 'a-zA-Z0-9._-'); echo "${_repo:-unknown}")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
||||||
if [ -f "$_PF" ]; then
|
if [ -f "$_PF" ]; then
|
||||||
|
|
@ -175,7 +175,7 @@ Only run `open` if yes. Always run `touch`.
|
||||||
|
|
||||||
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
||||||
|
|
||||||
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code, file paths, or repo names.
|
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code or file paths. Your repo name is recorded locally only and stripped before any upload.
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
- A) Help gstack get better! (recommended)
|
- A) Help gstack get better! (recommended)
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,7 @@ _QUESTION_TUNING=$(~/.claude/skills/gstack/bin/gstack-config get question_tuning
|
||||||
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
||||||
mkdir -p ~/.gstack/analytics
|
mkdir -p ~/.gstack/analytics
|
||||||
if [ "$_TEL" != "off" ]; then
|
if [ "$_TEL" != "off" ]; then
|
||||||
echo '{"skill":"context-save","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
|
echo '{"skill":"context-save","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(_repo=$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null | tr -cd 'a-zA-Z0-9._-'); echo "${_repo:-unknown}")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
||||||
if [ -f "$_PF" ]; then
|
if [ -f "$_PF" ]; then
|
||||||
|
|
@ -174,7 +174,7 @@ Only run `open` if yes. Always run `touch`.
|
||||||
|
|
||||||
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
||||||
|
|
||||||
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code, file paths, or repo names.
|
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code or file paths. Your repo name is recorded locally only and stripped before any upload.
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
- A) Help gstack get better! (recommended)
|
- A) Help gstack get better! (recommended)
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,7 @@ _QUESTION_TUNING=$(~/.claude/skills/gstack/bin/gstack-config get question_tuning
|
||||||
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
||||||
mkdir -p ~/.gstack/analytics
|
mkdir -p ~/.gstack/analytics
|
||||||
if [ "$_TEL" != "off" ]; then
|
if [ "$_TEL" != "off" ]; then
|
||||||
echo '{"skill":"cso","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
|
echo '{"skill":"cso","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(_repo=$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null | tr -cd 'a-zA-Z0-9._-'); echo "${_repo:-unknown}")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
||||||
if [ -f "$_PF" ]; then
|
if [ -f "$_PF" ]; then
|
||||||
|
|
@ -177,7 +177,7 @@ Only run `open` if yes. Always run `touch`.
|
||||||
|
|
||||||
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
||||||
|
|
||||||
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code, file paths, or repo names.
|
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code or file paths. Your repo name is recorded locally only and stripped before any upload.
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
- A) Help gstack get better! (recommended)
|
- A) Help gstack get better! (recommended)
|
||||||
|
|
|
||||||
|
|
@ -87,7 +87,7 @@ _QUESTION_TUNING=$(~/.claude/skills/gstack/bin/gstack-config get question_tuning
|
||||||
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
||||||
mkdir -p ~/.gstack/analytics
|
mkdir -p ~/.gstack/analytics
|
||||||
if [ "$_TEL" != "off" ]; then
|
if [ "$_TEL" != "off" ]; then
|
||||||
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
|
echo '{"skill":"design-consultation","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(_repo=$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null | tr -cd 'a-zA-Z0-9._-'); echo "${_repo:-unknown}")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
||||||
if [ -f "$_PF" ]; then
|
if [ -f "$_PF" ]; then
|
||||||
|
|
@ -197,7 +197,7 @@ Only run `open` if yes. Always run `touch`.
|
||||||
|
|
||||||
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
||||||
|
|
||||||
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code, file paths, or repo names.
|
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code or file paths. Your repo name is recorded locally only and stripped before any upload.
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
- A) Help gstack get better! (recommended)
|
- A) Help gstack get better! (recommended)
|
||||||
|
|
|
||||||
|
|
@ -68,7 +68,7 @@ _QUESTION_TUNING=$(~/.claude/skills/gstack/bin/gstack-config get question_tuning
|
||||||
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
||||||
mkdir -p ~/.gstack/analytics
|
mkdir -p ~/.gstack/analytics
|
||||||
if [ "$_TEL" != "off" ]; then
|
if [ "$_TEL" != "off" ]; then
|
||||||
echo '{"skill":"design-html","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
|
echo '{"skill":"design-html","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(_repo=$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null | tr -cd 'a-zA-Z0-9._-'); echo "${_repo:-unknown}")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
||||||
if [ -f "$_PF" ]; then
|
if [ -f "$_PF" ]; then
|
||||||
|
|
@ -178,7 +178,7 @@ Only run `open` if yes. Always run `touch`.
|
||||||
|
|
||||||
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
||||||
|
|
||||||
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code, file paths, or repo names.
|
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code or file paths. Your repo name is recorded locally only and stripped before any upload.
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
- A) Help gstack get better! (recommended)
|
- A) Help gstack get better! (recommended)
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,7 @@ _QUESTION_TUNING=$(~/.claude/skills/gstack/bin/gstack-config get question_tuning
|
||||||
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
||||||
mkdir -p ~/.gstack/analytics
|
mkdir -p ~/.gstack/analytics
|
||||||
if [ "$_TEL" != "off" ]; then
|
if [ "$_TEL" != "off" ]; then
|
||||||
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
|
echo '{"skill":"design-review","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(_repo=$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null | tr -cd 'a-zA-Z0-9._-'); echo "${_repo:-unknown}")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
||||||
if [ -f "$_PF" ]; then
|
if [ -f "$_PF" ]; then
|
||||||
|
|
@ -175,7 +175,7 @@ Only run `open` if yes. Always run `touch`.
|
||||||
|
|
||||||
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
||||||
|
|
||||||
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code, file paths, or repo names.
|
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code or file paths. Your repo name is recorded locally only and stripped before any upload.
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
- A) Help gstack get better! (recommended)
|
- A) Help gstack get better! (recommended)
|
||||||
|
|
|
||||||
|
|
@ -82,7 +82,7 @@ _QUESTION_TUNING=$(~/.claude/skills/gstack/bin/gstack-config get question_tuning
|
||||||
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
||||||
mkdir -p ~/.gstack/analytics
|
mkdir -p ~/.gstack/analytics
|
||||||
if [ "$_TEL" != "off" ]; then
|
if [ "$_TEL" != "off" ]; then
|
||||||
echo '{"skill":"design-shotgun","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
|
echo '{"skill":"design-shotgun","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(_repo=$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null | tr -cd 'a-zA-Z0-9._-'); echo "${_repo:-unknown}")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
||||||
if [ -f "$_PF" ]; then
|
if [ -f "$_PF" ]; then
|
||||||
|
|
@ -192,7 +192,7 @@ Only run `open` if yes. Always run `touch`.
|
||||||
|
|
||||||
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
||||||
|
|
||||||
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code, file paths, or repo names.
|
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code or file paths. Your repo name is recorded locally only and stripped before any upload.
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
- A) Help gstack get better! (recommended)
|
- A) Help gstack get better! (recommended)
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,7 @@ _QUESTION_TUNING=$(~/.claude/skills/gstack/bin/gstack-config get question_tuning
|
||||||
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
||||||
mkdir -p ~/.gstack/analytics
|
mkdir -p ~/.gstack/analytics
|
||||||
if [ "$_TEL" != "off" ]; then
|
if [ "$_TEL" != "off" ]; then
|
||||||
echo '{"skill":"devex-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
|
echo '{"skill":"devex-review","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(_repo=$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null | tr -cd 'a-zA-Z0-9._-'); echo "${_repo:-unknown}")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
||||||
if [ -f "$_PF" ]; then
|
if [ -f "$_PF" ]; then
|
||||||
|
|
@ -177,7 +177,7 @@ Only run `open` if yes. Always run `touch`.
|
||||||
|
|
||||||
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
||||||
|
|
||||||
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code, file paths, or repo names.
|
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code or file paths. Your repo name is recorded locally only and stripped before any upload.
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
- A) Help gstack get better! (recommended)
|
- A) Help gstack get better! (recommended)
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,7 @@ _QUESTION_TUNING=$(~/.claude/skills/gstack/bin/gstack-config get question_tuning
|
||||||
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
||||||
mkdir -p ~/.gstack/analytics
|
mkdir -p ~/.gstack/analytics
|
||||||
if [ "$_TEL" != "off" ]; then
|
if [ "$_TEL" != "off" ]; then
|
||||||
echo '{"skill":"document-generate","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
|
echo '{"skill":"document-generate","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(_repo=$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null | tr -cd 'a-zA-Z0-9._-'); echo "${_repo:-unknown}")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
||||||
if [ -f "$_PF" ]; then
|
if [ -f "$_PF" ]; then
|
||||||
|
|
@ -177,7 +177,7 @@ Only run `open` if yes. Always run `touch`.
|
||||||
|
|
||||||
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
||||||
|
|
||||||
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code, file paths, or repo names.
|
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code or file paths. Your repo name is recorded locally only and stripped before any upload.
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
- A) Help gstack get better! (recommended)
|
- A) Help gstack get better! (recommended)
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,7 @@ _QUESTION_TUNING=$(~/.claude/skills/gstack/bin/gstack-config get question_tuning
|
||||||
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
||||||
mkdir -p ~/.gstack/analytics
|
mkdir -p ~/.gstack/analytics
|
||||||
if [ "$_TEL" != "off" ]; then
|
if [ "$_TEL" != "off" ]; then
|
||||||
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
|
echo '{"skill":"document-release","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(_repo=$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null | tr -cd 'a-zA-Z0-9._-'); echo "${_repo:-unknown}")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
||||||
if [ -f "$_PF" ]; then
|
if [ -f "$_PF" ]; then
|
||||||
|
|
@ -175,7 +175,7 @@ Only run `open` if yes. Always run `touch`.
|
||||||
|
|
||||||
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
||||||
|
|
||||||
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code, file paths, or repo names.
|
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code or file paths. Your repo name is recorded locally only and stripped before any upload.
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
- A) Help gstack get better! (recommended)
|
- A) Help gstack get better! (recommended)
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,7 @@ Conventions:
|
||||||
- [/plan-devex-review](plan-devex-review/SKILL.md): Interactive developer experience plan review.
|
- [/plan-devex-review](plan-devex-review/SKILL.md): Interactive developer experience plan review.
|
||||||
- [/plan-eng-review](plan-eng-review/SKILL.md): Eng manager-mode plan review.
|
- [/plan-eng-review](plan-eng-review/SKILL.md): Eng manager-mode plan review.
|
||||||
- [/plan-tune](plan-tune/SKILL.md): Self-tuning question sensitivity + developer psychographic for gstack (v1: observational).
|
- [/plan-tune](plan-tune/SKILL.md): Self-tuning question sensitivity + developer psychographic for gstack (v1: observational).
|
||||||
|
- [/pr-prep](pr-prep/SKILL.md): Pre-PR upstream duplicate audit.
|
||||||
- [/qa](qa/SKILL.md): Systematically QA test a web application and fix bugs found.
|
- [/qa](qa/SKILL.md): Systematically QA test a web application and fix bugs found.
|
||||||
- [/qa-only](qa-only/SKILL.md): Report-only QA testing.
|
- [/qa-only](qa-only/SKILL.md): Report-only QA testing.
|
||||||
- [/retro](retro/SKILL.md): Weekly engineering retrospective.
|
- [/retro](retro/SKILL.md): Weekly engineering retrospective.
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,7 @@ _QUESTION_TUNING=$(~/.claude/skills/gstack/bin/gstack-config get question_tuning
|
||||||
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
||||||
mkdir -p ~/.gstack/analytics
|
mkdir -p ~/.gstack/analytics
|
||||||
if [ "$_TEL" != "off" ]; then
|
if [ "$_TEL" != "off" ]; then
|
||||||
echo '{"skill":"health","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
|
echo '{"skill":"health","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(_repo=$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null | tr -cd 'a-zA-Z0-9._-'); echo "${_repo:-unknown}")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
||||||
if [ -f "$_PF" ]; then
|
if [ -f "$_PF" ]; then
|
||||||
|
|
@ -173,7 +173,7 @@ Only run `open` if yes. Always run `touch`.
|
||||||
|
|
||||||
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
||||||
|
|
||||||
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code, file paths, or repo names.
|
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code or file paths. Your repo name is recorded locally only and stripped before any upload.
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
- A) Help gstack get better! (recommended)
|
- A) Help gstack get better! (recommended)
|
||||||
|
|
|
||||||
|
|
@ -102,7 +102,7 @@ _QUESTION_TUNING=$(~/.claude/skills/gstack/bin/gstack-config get question_tuning
|
||||||
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
||||||
mkdir -p ~/.gstack/analytics
|
mkdir -p ~/.gstack/analytics
|
||||||
if [ "$_TEL" != "off" ]; then
|
if [ "$_TEL" != "off" ]; then
|
||||||
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
|
echo '{"skill":"investigate","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(_repo=$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null | tr -cd 'a-zA-Z0-9._-'); echo "${_repo:-unknown}")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
||||||
if [ -f "$_PF" ]; then
|
if [ -f "$_PF" ]; then
|
||||||
|
|
@ -212,7 +212,7 @@ Only run `open` if yes. Always run `touch`.
|
||||||
|
|
||||||
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
||||||
|
|
||||||
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code, file paths, or repo names.
|
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code or file paths. Your repo name is recorded locally only and stripped before any upload.
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
- A) Help gstack get better! (recommended)
|
- A) Help gstack get better! (recommended)
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,7 @@ _QUESTION_TUNING=$(~/.claude/skills/gstack/bin/gstack-config get question_tuning
|
||||||
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
||||||
mkdir -p ~/.gstack/analytics
|
mkdir -p ~/.gstack/analytics
|
||||||
if [ "$_TEL" != "off" ]; then
|
if [ "$_TEL" != "off" ]; then
|
||||||
echo '{"skill":"ios-clean","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
|
echo '{"skill":"ios-clean","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(_repo=$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null | tr -cd 'a-zA-Z0-9._-'); echo "${_repo:-unknown}")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
||||||
if [ -f "$_PF" ]; then
|
if [ -f "$_PF" ]; then
|
||||||
|
|
@ -175,7 +175,7 @@ Only run `open` if yes. Always run `touch`.
|
||||||
|
|
||||||
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
||||||
|
|
||||||
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code, file paths, or repo names.
|
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code or file paths. Your repo name is recorded locally only and stripped before any upload.
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
- A) Help gstack get better! (recommended)
|
- A) Help gstack get better! (recommended)
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,7 @@ _QUESTION_TUNING=$(~/.claude/skills/gstack/bin/gstack-config get question_tuning
|
||||||
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
||||||
mkdir -p ~/.gstack/analytics
|
mkdir -p ~/.gstack/analytics
|
||||||
if [ "$_TEL" != "off" ]; then
|
if [ "$_TEL" != "off" ]; then
|
||||||
echo '{"skill":"ios-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
|
echo '{"skill":"ios-design-review","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(_repo=$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null | tr -cd 'a-zA-Z0-9._-'); echo "${_repo:-unknown}")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
||||||
if [ -f "$_PF" ]; then
|
if [ -f "$_PF" ]; then
|
||||||
|
|
@ -177,7 +177,7 @@ Only run `open` if yes. Always run `touch`.
|
||||||
|
|
||||||
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
||||||
|
|
||||||
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code, file paths, or repo names.
|
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code or file paths. Your repo name is recorded locally only and stripped before any upload.
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
- A) Help gstack get better! (recommended)
|
- A) Help gstack get better! (recommended)
|
||||||
|
|
|
||||||
|
|
@ -68,7 +68,7 @@ _QUESTION_TUNING=$(~/.claude/skills/gstack/bin/gstack-config get question_tuning
|
||||||
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
||||||
mkdir -p ~/.gstack/analytics
|
mkdir -p ~/.gstack/analytics
|
||||||
if [ "$_TEL" != "off" ]; then
|
if [ "$_TEL" != "off" ]; then
|
||||||
echo '{"skill":"ios-fix","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
|
echo '{"skill":"ios-fix","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(_repo=$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null | tr -cd 'a-zA-Z0-9._-'); echo "${_repo:-unknown}")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
||||||
if [ -f "$_PF" ]; then
|
if [ -f "$_PF" ]; then
|
||||||
|
|
@ -178,7 +178,7 @@ Only run `open` if yes. Always run `touch`.
|
||||||
|
|
||||||
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
||||||
|
|
||||||
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code, file paths, or repo names.
|
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code or file paths. Your repo name is recorded locally only and stripped before any upload.
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
- A) Help gstack get better! (recommended)
|
- A) Help gstack get better! (recommended)
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,7 @@ _QUESTION_TUNING=$(~/.claude/skills/gstack/bin/gstack-config get question_tuning
|
||||||
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
||||||
mkdir -p ~/.gstack/analytics
|
mkdir -p ~/.gstack/analytics
|
||||||
if [ "$_TEL" != "off" ]; then
|
if [ "$_TEL" != "off" ]; then
|
||||||
echo '{"skill":"ios-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
|
echo '{"skill":"ios-qa","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(_repo=$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null | tr -cd 'a-zA-Z0-9._-'); echo "${_repo:-unknown}")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
||||||
if [ -f "$_PF" ]; then
|
if [ -f "$_PF" ]; then
|
||||||
|
|
@ -181,7 +181,7 @@ Only run `open` if yes. Always run `touch`.
|
||||||
|
|
||||||
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
||||||
|
|
||||||
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code, file paths, or repo names.
|
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code or file paths. Your repo name is recorded locally only and stripped before any upload.
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
- A) Help gstack get better! (recommended)
|
- A) Help gstack get better! (recommended)
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,7 @@ _QUESTION_TUNING=$(~/.claude/skills/gstack/bin/gstack-config get question_tuning
|
||||||
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
||||||
mkdir -p ~/.gstack/analytics
|
mkdir -p ~/.gstack/analytics
|
||||||
if [ "$_TEL" != "off" ]; then
|
if [ "$_TEL" != "off" ]; then
|
||||||
echo '{"skill":"ios-sync","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
|
echo '{"skill":"ios-sync","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(_repo=$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null | tr -cd 'a-zA-Z0-9._-'); echo "${_repo:-unknown}")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
||||||
if [ -f "$_PF" ]; then
|
if [ -f "$_PF" ]; then
|
||||||
|
|
@ -175,7 +175,7 @@ Only run `open` if yes. Always run `touch`.
|
||||||
|
|
||||||
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
||||||
|
|
||||||
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code, file paths, or repo names.
|
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code or file paths. Your repo name is recorded locally only and stripped before any upload.
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
- A) Help gstack get better! (recommended)
|
- A) Help gstack get better! (recommended)
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,7 @@ _QUESTION_TUNING=$(~/.claude/skills/gstack/bin/gstack-config get question_tuning
|
||||||
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
||||||
mkdir -p ~/.gstack/analytics
|
mkdir -p ~/.gstack/analytics
|
||||||
if [ "$_TEL" != "off" ]; then
|
if [ "$_TEL" != "off" ]; then
|
||||||
echo '{"skill":"land-and-deploy","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
|
echo '{"skill":"land-and-deploy","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(_repo=$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null | tr -cd 'a-zA-Z0-9._-'); echo "${_repo:-unknown}")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
||||||
if [ -f "$_PF" ]; then
|
if [ -f "$_PF" ]; then
|
||||||
|
|
@ -170,7 +170,7 @@ Only run `open` if yes. Always run `touch`.
|
||||||
|
|
||||||
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
||||||
|
|
||||||
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code, file paths, or repo names.
|
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code or file paths. Your repo name is recorded locally only and stripped before any upload.
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
- A) Help gstack get better! (recommended)
|
- A) Help gstack get better! (recommended)
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,7 @@ _QUESTION_TUNING=$(~/.claude/skills/gstack/bin/gstack-config get question_tuning
|
||||||
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
||||||
mkdir -p ~/.gstack/analytics
|
mkdir -p ~/.gstack/analytics
|
||||||
if [ "$_TEL" != "off" ]; then
|
if [ "$_TEL" != "off" ]; then
|
||||||
echo '{"skill":"landing-report","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
|
echo '{"skill":"landing-report","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(_repo=$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null | tr -cd 'a-zA-Z0-9._-'); echo "${_repo:-unknown}")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
||||||
if [ -f "$_PF" ]; then
|
if [ -f "$_PF" ]; then
|
||||||
|
|
@ -171,7 +171,7 @@ Only run `open` if yes. Always run `touch`.
|
||||||
|
|
||||||
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
||||||
|
|
||||||
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code, file paths, or repo names.
|
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code or file paths. Your repo name is recorded locally only and stripped before any upload.
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
- A) Help gstack get better! (recommended)
|
- A) Help gstack get better! (recommended)
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,7 @@ _QUESTION_TUNING=$(~/.claude/skills/gstack/bin/gstack-config get question_tuning
|
||||||
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
||||||
mkdir -p ~/.gstack/analytics
|
mkdir -p ~/.gstack/analytics
|
||||||
if [ "$_TEL" != "off" ]; then
|
if [ "$_TEL" != "off" ]; then
|
||||||
echo '{"skill":"learn","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
|
echo '{"skill":"learn","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(_repo=$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null | tr -cd 'a-zA-Z0-9._-'); echo "${_repo:-unknown}")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
||||||
if [ -f "$_PF" ]; then
|
if [ -f "$_PF" ]; then
|
||||||
|
|
@ -173,7 +173,7 @@ Only run `open` if yes. Always run `touch`.
|
||||||
|
|
||||||
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
||||||
|
|
||||||
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code, file paths, or repo names.
|
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code or file paths. Your repo name is recorded locally only and stripped before any upload.
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
- A) Help gstack get better! (recommended)
|
- A) Help gstack get better! (recommended)
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@ _QUESTION_TUNING=$(~/.claude/skills/gstack/bin/gstack-config get question_tuning
|
||||||
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
||||||
mkdir -p ~/.gstack/analytics
|
mkdir -p ~/.gstack/analytics
|
||||||
if [ "$_TEL" != "off" ]; then
|
if [ "$_TEL" != "off" ]; then
|
||||||
echo '{"skill":"make-pdf","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
|
echo '{"skill":"make-pdf","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(_repo=$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null | tr -cd 'a-zA-Z0-9._-'); echo "${_repo:-unknown}")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
||||||
if [ -f "$_PF" ]; then
|
if [ -f "$_PF" ]; then
|
||||||
|
|
@ -208,7 +208,7 @@ Only run `open` if yes. Always run `touch`.
|
||||||
|
|
||||||
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
||||||
|
|
||||||
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code, file paths, or repo names.
|
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code or file paths. Your repo name is recorded locally only and stripped before any upload.
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
- A) Help gstack get better! (recommended)
|
- A) Help gstack get better! (recommended)
|
||||||
|
|
|
||||||
|
|
@ -98,7 +98,7 @@ _QUESTION_TUNING=$(~/.claude/skills/gstack/bin/gstack-config get question_tuning
|
||||||
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
||||||
mkdir -p ~/.gstack/analytics
|
mkdir -p ~/.gstack/analytics
|
||||||
if [ "$_TEL" != "off" ]; then
|
if [ "$_TEL" != "off" ]; then
|
||||||
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
|
echo '{"skill":"office-hours","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(_repo=$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null | tr -cd 'a-zA-Z0-9._-'); echo "${_repo:-unknown}")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
||||||
if [ -f "$_PF" ]; then
|
if [ -f "$_PF" ]; then
|
||||||
|
|
@ -208,7 +208,7 @@ Only run `open` if yes. Always run `touch`.
|
||||||
|
|
||||||
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
||||||
|
|
||||||
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code, file paths, or repo names.
|
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code or file paths. Your repo name is recorded locally only and stripped before any upload.
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
- A) Help gstack get better! (recommended)
|
- A) Help gstack get better! (recommended)
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,7 @@ _QUESTION_TUNING=$(~/.claude/skills/gstack/bin/gstack-config get question_tuning
|
||||||
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
||||||
mkdir -p ~/.gstack/analytics
|
mkdir -p ~/.gstack/analytics
|
||||||
if [ "$_TEL" != "off" ]; then
|
if [ "$_TEL" != "off" ]; then
|
||||||
echo '{"skill":"open-gstack-browser","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
|
echo '{"skill":"open-gstack-browser","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(_repo=$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null | tr -cd 'a-zA-Z0-9._-'); echo "${_repo:-unknown}")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
||||||
if [ -f "$_PF" ]; then
|
if [ -f "$_PF" ]; then
|
||||||
|
|
@ -170,7 +170,7 @@ Only run `open` if yes. Always run `touch`.
|
||||||
|
|
||||||
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
||||||
|
|
||||||
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code, file paths, or repo names.
|
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code or file paths. Your repo name is recorded locally only and stripped before any upload.
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
- A) Help gstack get better! (recommended)
|
- A) Help gstack get better! (recommended)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "gstack",
|
"name": "gstack",
|
||||||
"version": "1.55.0.0",
|
"version": "1.55.1.0",
|
||||||
"description": "Garry's Stack — Claude Code skills + fast headless browser. One repo, one install, entire AI engineering workflow.",
|
"description": "Garry's Stack — Claude Code skills + fast headless browser. One repo, one install, entire AI engineering workflow.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@ _QUESTION_TUNING=$(~/.claude/skills/gstack/bin/gstack-config get question_tuning
|
||||||
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
||||||
mkdir -p ~/.gstack/analytics
|
mkdir -p ~/.gstack/analytics
|
||||||
if [ "$_TEL" != "off" ]; then
|
if [ "$_TEL" != "off" ]; then
|
||||||
echo '{"skill":"pair-agent","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
|
echo '{"skill":"pair-agent","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(_repo=$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null | tr -cd 'a-zA-Z0-9._-'); echo "${_repo:-unknown}")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
||||||
if [ -f "$_PF" ]; then
|
if [ -f "$_PF" ]; then
|
||||||
|
|
@ -172,7 +172,7 @@ Only run `open` if yes. Always run `touch`.
|
||||||
|
|
||||||
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
||||||
|
|
||||||
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code, file paths, or repo names.
|
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code or file paths. Your repo name is recorded locally only and stripped before any upload.
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
- A) Help gstack get better! (recommended)
|
- A) Help gstack get better! (recommended)
|
||||||
|
|
|
||||||
|
|
@ -92,7 +92,7 @@ _QUESTION_TUNING=$(~/.claude/skills/gstack/bin/gstack-config get question_tuning
|
||||||
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
||||||
mkdir -p ~/.gstack/analytics
|
mkdir -p ~/.gstack/analytics
|
||||||
if [ "$_TEL" != "off" ]; then
|
if [ "$_TEL" != "off" ]; then
|
||||||
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
|
echo '{"skill":"plan-ceo-review","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(_repo=$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null | tr -cd 'a-zA-Z0-9._-'); echo "${_repo:-unknown}")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
||||||
if [ -f "$_PF" ]; then
|
if [ -f "$_PF" ]; then
|
||||||
|
|
@ -202,7 +202,7 @@ Only run `open` if yes. Always run `touch`.
|
||||||
|
|
||||||
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
||||||
|
|
||||||
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code, file paths, or repo names.
|
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code or file paths. Your repo name is recorded locally only and stripped before any upload.
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
- A) Help gstack get better! (recommended)
|
- A) Help gstack get better! (recommended)
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,7 @@ _QUESTION_TUNING=$(~/.claude/skills/gstack/bin/gstack-config get question_tuning
|
||||||
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
||||||
mkdir -p ~/.gstack/analytics
|
mkdir -p ~/.gstack/analytics
|
||||||
if [ "$_TEL" != "off" ]; then
|
if [ "$_TEL" != "off" ]; then
|
||||||
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
|
echo '{"skill":"plan-design-review","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(_repo=$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null | tr -cd 'a-zA-Z0-9._-'); echo "${_repo:-unknown}")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
||||||
if [ -f "$_PF" ]; then
|
if [ -f "$_PF" ]; then
|
||||||
|
|
@ -174,7 +174,7 @@ Only run `open` if yes. Always run `touch`.
|
||||||
|
|
||||||
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
||||||
|
|
||||||
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code, file paths, or repo names.
|
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code or file paths. Your repo name is recorded locally only and stripped before any upload.
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
- A) Help gstack get better! (recommended)
|
- A) Help gstack get better! (recommended)
|
||||||
|
|
|
||||||
|
|
@ -70,7 +70,7 @@ _QUESTION_TUNING=$(~/.claude/skills/gstack/bin/gstack-config get question_tuning
|
||||||
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
||||||
mkdir -p ~/.gstack/analytics
|
mkdir -p ~/.gstack/analytics
|
||||||
if [ "$_TEL" != "off" ]; then
|
if [ "$_TEL" != "off" ]; then
|
||||||
echo '{"skill":"plan-devex-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
|
echo '{"skill":"plan-devex-review","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(_repo=$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null | tr -cd 'a-zA-Z0-9._-'); echo "${_repo:-unknown}")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
||||||
if [ -f "$_PF" ]; then
|
if [ -f "$_PF" ]; then
|
||||||
|
|
@ -180,7 +180,7 @@ Only run `open` if yes. Always run `touch`.
|
||||||
|
|
||||||
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
||||||
|
|
||||||
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code, file paths, or repo names.
|
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code or file paths. Your repo name is recorded locally only and stripped before any upload.
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
- A) Help gstack get better! (recommended)
|
- A) Help gstack get better! (recommended)
|
||||||
|
|
|
||||||
|
|
@ -68,7 +68,7 @@ _QUESTION_TUNING=$(~/.claude/skills/gstack/bin/gstack-config get question_tuning
|
||||||
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
||||||
mkdir -p ~/.gstack/analytics
|
mkdir -p ~/.gstack/analytics
|
||||||
if [ "$_TEL" != "off" ]; then
|
if [ "$_TEL" != "off" ]; then
|
||||||
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
|
echo '{"skill":"plan-eng-review","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(_repo=$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null | tr -cd 'a-zA-Z0-9._-'); echo "${_repo:-unknown}")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
||||||
if [ -f "$_PF" ]; then
|
if [ -f "$_PF" ]; then
|
||||||
|
|
@ -178,7 +178,7 @@ Only run `open` if yes. Always run `touch`.
|
||||||
|
|
||||||
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
||||||
|
|
||||||
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code, file paths, or repo names.
|
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code or file paths. Your repo name is recorded locally only and stripped before any upload.
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
- A) Help gstack get better! (recommended)
|
- A) Help gstack get better! (recommended)
|
||||||
|
|
|
||||||
|
|
@ -73,7 +73,7 @@ _QUESTION_TUNING=$(~/.claude/skills/gstack/bin/gstack-config get question_tuning
|
||||||
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
||||||
mkdir -p ~/.gstack/analytics
|
mkdir -p ~/.gstack/analytics
|
||||||
if [ "$_TEL" != "off" ]; then
|
if [ "$_TEL" != "off" ]; then
|
||||||
echo '{"skill":"plan-tune","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
|
echo '{"skill":"plan-tune","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(_repo=$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null | tr -cd 'a-zA-Z0-9._-'); echo "${_repo:-unknown}")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
||||||
if [ -f "$_PF" ]; then
|
if [ -f "$_PF" ]; then
|
||||||
|
|
@ -183,7 +183,7 @@ Only run `open` if yes. Always run `touch`.
|
||||||
|
|
||||||
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
||||||
|
|
||||||
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code, file paths, or repo names.
|
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code or file paths. Your repo name is recorded locally only and stripped before any upload.
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
- A) Help gstack get better! (recommended)
|
- A) Help gstack get better! (recommended)
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,347 @@
|
||||||
|
---
|
||||||
|
name: pr-prep
|
||||||
|
preamble-tier: 4
|
||||||
|
version: 0.1.0
|
||||||
|
description: |
|
||||||
|
Pre-PR upstream duplicate audit. Walks `git log base..HEAD`, derives
|
||||||
|
search keywords from commit subjects + changed file paths, queries
|
||||||
|
upstream issues + PRs via `gh`, scores each commit against upstream
|
||||||
|
collisions (EXACT_DUP / OVERLAP / SIBLING / CLEAN), and refuses to
|
||||||
|
proceed when EXACT_DUP found. Use when asked to "audit my PR",
|
||||||
|
"check for duplicates", "pr-prep", "is this already filed",
|
||||||
|
"upstream check before PR", or "pre-PR audit".
|
||||||
|
Proactively invoke this skill (do NOT skip the audit) before any
|
||||||
|
`gh pr create` against a tracked upstream repo. Hooks into /ship
|
||||||
|
as Step 0. (gstack)
|
||||||
|
allowed-tools:
|
||||||
|
- Bash
|
||||||
|
- Read
|
||||||
|
- Grep
|
||||||
|
- Glob
|
||||||
|
- AskUserQuestion
|
||||||
|
triggers:
|
||||||
|
- pr-prep
|
||||||
|
- audit my PR
|
||||||
|
- check for duplicates
|
||||||
|
- upstream check
|
||||||
|
- pre-PR audit
|
||||||
|
- is this already filed
|
||||||
|
- dup PR check
|
||||||
|
---
|
||||||
|
|
||||||
|
{{PREAMBLE}}
|
||||||
|
|
||||||
|
{{BASE_BRANCH_DETECT}}
|
||||||
|
|
||||||
|
# pr-prep: Pre-PR Upstream Duplicate Audit
|
||||||
|
|
||||||
|
You are running the `/pr-prep` workflow. This is a **read-only audit** that
|
||||||
|
verifies your branch's commits against upstream issues + PRs before you
|
||||||
|
file a duplicate. Refuses to proceed only on hard duplicates; everything
|
||||||
|
else is informational.
|
||||||
|
|
||||||
|
**Why this exists:** every contributor faces the upstream-dup risk.
|
||||||
|
Open issues sit for weeks. Multiple PRs converge on the same surface.
|
||||||
|
Filing a dup wastes reviewer time, contributor goodwill, and your own
|
||||||
|
branch cleanup. This skill catches dups in ~30s of `gh` queries
|
||||||
|
*before* the PR exists.
|
||||||
|
|
||||||
|
**Output:** per-commit collision report with severity buckets +
|
||||||
|
recommended action.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 1: Pre-flight
|
||||||
|
|
||||||
|
1. Run `git status` (never with `-uall`). Working tree must be clean
|
||||||
|
or have only the commits-being-audited. Abort cleanly if the
|
||||||
|
working tree has unrelated mid-edit state.
|
||||||
|
|
||||||
|
2. Determine the base branch (already set by `{{BASE_BRANCH_DETECT}}`
|
||||||
|
into `$BASE_BRANCH`). Honor `--base <name>` flag override.
|
||||||
|
|
||||||
|
3. Resolve the upstream repo via `gh repo view --json nameWithOwner -q .nameWithOwner`.
|
||||||
|
Default uses `origin`; override via `--repo owner/name`.
|
||||||
|
|
||||||
|
4. **Read the upstream `CONTRIBUTING.md` (if present)** and surface its
|
||||||
|
pre-push gates + test requirements so the agent knows what must
|
||||||
|
pass BEFORE filing. Cache to `/tmp/pr-prep-contributing.md` for
|
||||||
|
the rest of the run.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
gh api "repos/$REPO/contents/CONTRIBUTING.md" --jq .content 2>/dev/null \
|
||||||
|
| base64 -d > /tmp/pr-prep-contributing.md || \
|
||||||
|
gh api "repos/$REPO/contents/contributing.md" --jq .content 2>/dev/null \
|
||||||
|
| base64 -d > /tmp/pr-prep-contributing.md || \
|
||||||
|
echo "" > /tmp/pr-prep-contributing.md
|
||||||
|
```
|
||||||
|
|
||||||
|
Extract + echo at this step (no need to dump the whole file in the
|
||||||
|
final report — the agent uses it inline when writing PR bodies):
|
||||||
|
- Required pre-push commands (e.g. `bun run verify`, `npm test`,
|
||||||
|
`cargo test`). Look for "before pushing", "pre-push", "verify",
|
||||||
|
"must pass", "required" headings.
|
||||||
|
- Test layout conventions (where do unit / e2e / regression tests
|
||||||
|
belong). Look for "Writing tests", "test structure" sections.
|
||||||
|
- Branch naming / commit message conventions. Look for "branch
|
||||||
|
name", "commit format", "conventional commits".
|
||||||
|
- Welcomed PR areas (if listed). Skips contributions that conflict
|
||||||
|
with the repo's roadmap.
|
||||||
|
- Banned patterns (e.g. "never add to allowlist", "no new mocks",
|
||||||
|
"no breaking changes"). Treat as hard gates.
|
||||||
|
|
||||||
|
5. Sanity-check: at least 1 commit in `$BASE_BRANCH..HEAD`. If zero,
|
||||||
|
abort with "no commits to audit; you're already on $BASE_BRANCH".
|
||||||
|
|
||||||
|
```bash
|
||||||
|
BASE="${BASE_BRANCH:-main}"
|
||||||
|
REPO=$(gh repo view --json nameWithOwner -q .nameWithOwner 2>/dev/null)
|
||||||
|
COMMITS=$(git log "$BASE"..HEAD --pretty='%H' 2>/dev/null)
|
||||||
|
if [ -z "$COMMITS" ]; then
|
||||||
|
echo "No commits to audit: $(git branch --show-current) is at $BASE."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
echo "Auditing $(echo "$COMMITS" | wc -l | tr -d ' ') commits against $REPO@$BASE"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Step 2: Walk each commit, extract search signals
|
||||||
|
|
||||||
|
For each commit hash:
|
||||||
|
- **Subject**: `git show -s --format=%s <sha>` — strip conventional-commit
|
||||||
|
prefix (`fix(scope):`, `feat:`, `chore(deps):`, etc).
|
||||||
|
- **Changed files**: `git show --stat --name-only --format= <sha>`.
|
||||||
|
- **Keywords**: from subject, drop stop words + verbs (fix/add/update/
|
||||||
|
bump/remove). Keep 3-6 meaningful tokens.
|
||||||
|
|
||||||
|
Build a search query per commit by joining keywords. Example:
|
||||||
|
- Subject: `fix(synopsis): tail-truncate documentText for small-model chat handlers`
|
||||||
|
- Keywords: `synopsis tail-truncate documentText small-model chat`
|
||||||
|
- Query: `synopsis documentText truncate`
|
||||||
|
|
||||||
|
Cap query to ~5 tokens. Too long → zero matches. Too short → noisy
|
||||||
|
matches.
|
||||||
|
|
||||||
|
## Step 3: Query upstream issues + PRs
|
||||||
|
|
||||||
|
For each commit's query, run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Open issues + PRs (highest collision risk)
|
||||||
|
gh issue list --repo "$REPO" --state open --search "$QUERY" --limit 8 --json number,title,url,labels
|
||||||
|
gh pr list --repo "$REPO" --state open --search "$QUERY" --limit 8 --json number,title,url,headRefName,author
|
||||||
|
|
||||||
|
# Closed in last 90 days (might be unreleased master fix)
|
||||||
|
gh issue list --repo "$REPO" --state closed --search "$QUERY" --limit 5 --json number,title,url,closedAt
|
||||||
|
gh pr list --repo "$REPO" --state merged --search "$QUERY" --limit 5 --json number,title,url,mergedAt
|
||||||
|
```
|
||||||
|
|
||||||
|
Hard guard: skip if the search call returns rate-limit (HTTP 429).
|
||||||
|
Print warning + suggest `gh auth refresh`. Don't false-clear on
|
||||||
|
rate-limit silence.
|
||||||
|
|
||||||
|
## Step 4: Score each upstream hit
|
||||||
|
|
||||||
|
For every issue/PR returned, compute a collision score:
|
||||||
|
|
||||||
|
- **Title token overlap (Jaccard)**: intersect commit-subject keywords
|
||||||
|
with upstream-title keywords. ≥0.5 = strong match.
|
||||||
|
- **File overlap (open PRs only)**: `gh pr diff <number> --name-only`
|
||||||
|
vs the commit's changed files. ≥0.5 = strong match.
|
||||||
|
- **State weighting**:
|
||||||
|
- OPEN PR → 1.0× (highest dup risk)
|
||||||
|
- OPEN issue → 0.7× (someone's tracking it)
|
||||||
|
- MERGED last 14 days → 0.6× (might be unreleased)
|
||||||
|
- CLOSED issue → 0.2× (low risk, but useful context)
|
||||||
|
|
||||||
|
Final severity bucket per commit:
|
||||||
|
|
||||||
|
| Bucket | Trigger |
|
||||||
|
|---|---|
|
||||||
|
| **EXACT_DUP** | Any OPEN PR with title Jaccard ≥0.6 OR file overlap ≥0.6 |
|
||||||
|
| **OVERLAP** | Any OPEN PR/issue with score ≥0.3, or ≥3 OPEN issues |
|
||||||
|
| **SIBLING** | OPEN issues but no PR; or merged-recently with overlap |
|
||||||
|
| **CLEAN** | No hits, or only old closed issues |
|
||||||
|
|
||||||
|
## Step 4.4: Second-opinion review via codex (CLEAN commits only)
|
||||||
|
|
||||||
|
For each commit bucketed CLEAN (i.e. not duplicating upstream work),
|
||||||
|
run an independent second-opinion code review BEFORE the PR is opened.
|
||||||
|
Catches bugs the author missed without spending reviewer attention
|
||||||
|
upstream.
|
||||||
|
|
||||||
|
The skill assumes `codex` CLI is on PATH (OpenAI's official CLI;
|
||||||
|
`brew install codex` on macOS). If absent, emit a soft warning + skip
|
||||||
|
this step — don't block. Different model family from Claude gives
|
||||||
|
genuine independent signal.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
if command -v codex >/dev/null 2>&1; then
|
||||||
|
for sha in $CLEAN_COMMIT_SHAS; do
|
||||||
|
diff=$(git show "$sha" --stat --pretty=format:"%s")
|
||||||
|
subject=$(git log -1 --format=%s "$sha")
|
||||||
|
codex review "Review commit ${sha:0:8} '${subject}' for correctness,
|
||||||
|
edge cases, and CONTRIBUTING.md compliance. Focus on: regression
|
||||||
|
risk on adjacent code paths, missing tests, hash/version-bump
|
||||||
|
invariants if touching cache keys, ordering bugs if touching
|
||||||
|
conditionals or dispatchers. Flag P0/P1/P2 issues with file:line."
|
||||||
|
done
|
||||||
|
else
|
||||||
|
echo "[pr-prep] codex CLI not found — skipping second-opinion review.
|
||||||
|
Install via 'brew install codex' or pin a fork-specific reviewer in
|
||||||
|
your skill config."
|
||||||
|
fi
|
||||||
|
```
|
||||||
|
|
||||||
|
Surface findings in the report under each commit as a `Codex P{N}`
|
||||||
|
line. P0/P1 findings escalate the commit's severity to OVERLAP at
|
||||||
|
minimum (don't file as CLEAN until addressed). P2 findings stay
|
||||||
|
CLEAN — author decides whether to fix-before-file or note-in-PR-body.
|
||||||
|
|
||||||
|
Real-world example (2026-05-26 motivating case):
|
||||||
|
- PR #1427 (synopsis doc truncate) → codex P2: env-overridable cap
|
||||||
|
not folded into `computeCorpusGeneration` hash. Different caps
|
||||||
|
produce same `corpus_generation` → cache invalidation breaks.
|
||||||
|
Fixed pre-merge, pushed as follow-up commit, comment posted to PR.
|
||||||
|
- PR #1428 (models doctor args[0]) → codex P2: `--help` regressed
|
||||||
|
into running network probes. Reorder ternary so `hasHelp` checked
|
||||||
|
first. Fixed pre-merge.
|
||||||
|
|
||||||
|
Both findings were structural, not stylistic. Author missed them
|
||||||
|
during own write-up. Net cost avoided: 2 review-cycle ping-pongs
|
||||||
|
upstream + a follow-up fix PR per finding.
|
||||||
|
|
||||||
|
## Step 4.5: Surface CONTRIBUTING.md pre-push gates per commit
|
||||||
|
|
||||||
|
For each commit that survives audit (CLEAN / OVERLAP / SIBLING — not
|
||||||
|
EXACT_DUP), check whether the changed files trigger any
|
||||||
|
CONTRIBUTING.md-stated test path. Example: a commit touching
|
||||||
|
`src/core/search/*` should run the eval-replay loop per the gbrain
|
||||||
|
CONTRIBUTING.md "Trigger paths" section.
|
||||||
|
|
||||||
|
Annotate each CLEAN/OVERLAP/SIBLING row with:
|
||||||
|
|
||||||
|
```
|
||||||
|
Pre-push gate: bun run verify (from CONTRIBUTING.md)
|
||||||
|
Trigger paths matched: none (no retrieval / no special test required)
|
||||||
|
Tests added in commit: yes / no / not required
|
||||||
|
```
|
||||||
|
|
||||||
|
If `Tests added: no` AND `not required` is unclear, surface as a
|
||||||
|
soft warning in the report but don't block — let the human decide.
|
||||||
|
|
||||||
|
## Step 5: Render report
|
||||||
|
|
||||||
|
Markdown table per commit:
|
||||||
|
|
||||||
|
```
|
||||||
|
## Audit: branch `feat/foo-bar` vs garrytan/gstack@main
|
||||||
|
|
||||||
|
### commit 20ed0eee fix(models): dispatch subcommand reads args[0] not args[1]
|
||||||
|
|
||||||
|
| Severity | # | Title | State | Author | Score |
|
||||||
|
|---|---|---|---|---|---|
|
||||||
|
| CLEAN | — | (no matches above threshold) | — | — | — |
|
||||||
|
|
||||||
|
**Action:** safe to file.
|
||||||
|
|
||||||
|
### commit ac213aa6 feat(synopsis): tail-truncate documentText
|
||||||
|
|
||||||
|
| Severity | # | Title | State | Author | Score |
|
||||||
|
|---|---|---|---|---|---|
|
||||||
|
| EXACT_DUP | #1358 | fix: allow contextual synopsis model env override | OPEN | lost9999 | 0.78 |
|
||||||
|
| OVERLAP | #1356 | fix: classify contextual synopsis transient errors | OPEN | lost9999 | 0.34 |
|
||||||
|
|
||||||
|
**Action:** close mine, comment on #1358 with my angle. DO NOT file new PR.
|
||||||
|
```
|
||||||
|
|
||||||
|
Print summary at end:
|
||||||
|
|
||||||
|
```
|
||||||
|
Summary: 1 EXACT_DUP, 1 CLEAN. 1 commit blocked.
|
||||||
|
```
|
||||||
|
|
||||||
|
## Step 6: Refusal on EXACT_DUP
|
||||||
|
|
||||||
|
If ANY commit is EXACT_DUP and `--force` is NOT set, exit non-zero
|
||||||
|
with a pinpoint message:
|
||||||
|
|
||||||
|
```
|
||||||
|
✗ Blocked: 1 commit duplicates open upstream work.
|
||||||
|
|
||||||
|
- ac213aa6 → #1358 (lost9999, OPEN 14d)
|
||||||
|
|
||||||
|
Resolutions:
|
||||||
|
1. Close your version, comment on #1358 with your angle.
|
||||||
|
2. Cherry-pick the unique parts to a new branch + file separately.
|
||||||
|
3. Override with `/pr-prep --force` if you've coordinated with
|
||||||
|
the existing PR author.
|
||||||
|
```
|
||||||
|
|
||||||
|
Always exit 0 on OVERLAP / SIBLING / CLEAN — those are informational.
|
||||||
|
|
||||||
|
## Step 7: /ship integration
|
||||||
|
|
||||||
|
When invoked by `/ship` (env `GSTACK_FROM_SHIP=1`):
|
||||||
|
- Skip the interactive AskUserQuestion confirmations
|
||||||
|
- Exit 0 on CLEAN/OVERLAP/SIBLING
|
||||||
|
- Exit 1 on EXACT_DUP (blocks /ship)
|
||||||
|
- Print machine-readable JSON to a known path so /ship can render
|
||||||
|
collisions in the PR body
|
||||||
|
|
||||||
|
## Flags
|
||||||
|
|
||||||
|
| Flag | Default | Effect |
|
||||||
|
|---|---|---|
|
||||||
|
| `--base <name>` | `main` | Base branch for commit walk |
|
||||||
|
| `--repo owner/name` | from `gh repo view` | Upstream repo for queries |
|
||||||
|
| `--force` | off | Proceed past EXACT_DUP (still print report) |
|
||||||
|
| `--json` | off | Machine-readable output, no markdown table |
|
||||||
|
| `--limit N` | 8 | Per-query result cap |
|
||||||
|
| `--no-file-diff` | off | Skip `gh pr diff` calls (faster, less accurate) |
|
||||||
|
|
||||||
|
## Cost + speed
|
||||||
|
|
||||||
|
- ~30-60s for a 5-commit branch
|
||||||
|
- 4 `gh` calls per commit (open issues, open PRs, closed issues, merged PRs)
|
||||||
|
- 1 extra `gh pr diff` per OPEN PR hit (capped at 5)
|
||||||
|
- ~25-50 `gh` calls total on a typical branch
|
||||||
|
- gh CLI personal rate limit: 5000/hr authenticated. Safe headroom.
|
||||||
|
|
||||||
|
## Real-world example (motivating case 2026-05-26)
|
||||||
|
|
||||||
|
User's branch on `garrytan/gbrain` had 8 commits ready for upstream
|
||||||
|
PRs. Without pr-prep, 4 of 4 unverified commits would have been
|
||||||
|
duplicates:
|
||||||
|
- `e96332c5` (reindex CLI_ONLY one-char fix) → #913 OPEN 14 days, same fix
|
||||||
|
- `74819cec` (sourceId fallback) → #836 OPEN, threads sourceId
|
||||||
|
- `787da2af` + `829099f9` (synopsis env-override) → #1358 OPEN, same env-override
|
||||||
|
- `e0133d8a` (LM Studio recipe) → #1051 + #1329, crowded space
|
||||||
|
|
||||||
|
Cost avoided: 4 noise PRs, 4 reviewer triage rounds, contributor
|
||||||
|
goodwill hit, 4 branch closures. pr-prep catches all 4 in ~45s.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Implementation note for future maintainers
|
||||||
|
|
||||||
|
Two reasonable build modes:
|
||||||
|
1. **Inline bash in SKILL.md** (current) — agent walks the steps,
|
||||||
|
composes `gh` calls, computes Jaccard via `comm` + `wc`. Slower,
|
||||||
|
more transparent.
|
||||||
|
2. **Helper script `bin/gstack-pr-prep`** — bash entry point that
|
||||||
|
does the heavy lifting, agent just orchestrates + renders.
|
||||||
|
Faster, more testable. Migration path when v0.2.0 lands tests.
|
||||||
|
|
||||||
|
v0.1.0 ships mode 1 because it's reviewable in a single file. v0.2.0
|
||||||
|
should move the Jaccard math + report rendering into `bin/`.
|
||||||
|
|
||||||
|
## Out of scope (v0.1.0)
|
||||||
|
|
||||||
|
- Diff-content (not just file-name) similarity scoring. Useful but
|
||||||
|
expensive (`gh pr diff` × N × full body).
|
||||||
|
- Cross-repo audit (e.g., fix in fork A applies to upstream B).
|
||||||
|
- LLM-judged semantic dup detection. Out of scope for a deterministic
|
||||||
|
pre-flight check.
|
||||||
|
- Auto-comment on the upstream PR. Owner must decide what to say.
|
||||||
|
|
||||||
|
These belong in a v0.2+ wave once the deterministic gate proves out.
|
||||||
|
|
@ -63,7 +63,7 @@ _QUESTION_TUNING=$(~/.claude/skills/gstack/bin/gstack-config get question_tuning
|
||||||
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
||||||
mkdir -p ~/.gstack/analytics
|
mkdir -p ~/.gstack/analytics
|
||||||
if [ "$_TEL" != "off" ]; then
|
if [ "$_TEL" != "off" ]; then
|
||||||
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
|
echo '{"skill":"qa-only","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(_repo=$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null | tr -cd 'a-zA-Z0-9._-'); echo "${_repo:-unknown}")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
||||||
if [ -f "$_PF" ]; then
|
if [ -f "$_PF" ]; then
|
||||||
|
|
@ -173,7 +173,7 @@ Only run `open` if yes. Always run `touch`.
|
||||||
|
|
||||||
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
||||||
|
|
||||||
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code, file paths, or repo names.
|
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code or file paths. Your repo name is recorded locally only and stripped before any upload.
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
- A) Help gstack get better! (recommended)
|
- A) Help gstack get better! (recommended)
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,7 @@ _QUESTION_TUNING=$(~/.claude/skills/gstack/bin/gstack-config get question_tuning
|
||||||
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
||||||
mkdir -p ~/.gstack/analytics
|
mkdir -p ~/.gstack/analytics
|
||||||
if [ "$_TEL" != "off" ]; then
|
if [ "$_TEL" != "off" ]; then
|
||||||
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
|
echo '{"skill":"qa","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(_repo=$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null | tr -cd 'a-zA-Z0-9._-'); echo "${_repo:-unknown}")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
||||||
if [ -f "$_PF" ]; then
|
if [ -f "$_PF" ]; then
|
||||||
|
|
@ -179,7 +179,7 @@ Only run `open` if yes. Always run `touch`.
|
||||||
|
|
||||||
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
||||||
|
|
||||||
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code, file paths, or repo names.
|
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code or file paths. Your repo name is recorded locally only and stripped before any upload.
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
- A) Help gstack get better! (recommended)
|
- A) Help gstack get better! (recommended)
|
||||||
|
|
|
||||||
|
|
@ -80,7 +80,7 @@ _QUESTION_TUNING=$(~/.claude/skills/gstack/bin/gstack-config get question_tuning
|
||||||
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
||||||
mkdir -p ~/.gstack/analytics
|
mkdir -p ~/.gstack/analytics
|
||||||
if [ "$_TEL" != "off" ]; then
|
if [ "$_TEL" != "off" ]; then
|
||||||
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
|
echo '{"skill":"retro","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(_repo=$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null | tr -cd 'a-zA-Z0-9._-'); echo "${_repo:-unknown}")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
||||||
if [ -f "$_PF" ]; then
|
if [ -f "$_PF" ]; then
|
||||||
|
|
@ -190,7 +190,7 @@ Only run `open` if yes. Always run `touch`.
|
||||||
|
|
||||||
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
||||||
|
|
||||||
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code, file paths, or repo names.
|
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code or file paths. Your repo name is recorded locally only and stripped before any upload.
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
- A) Help gstack get better! (recommended)
|
- A) Help gstack get better! (recommended)
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,7 @@ _QUESTION_TUNING=$(~/.claude/skills/gstack/bin/gstack-config get question_tuning
|
||||||
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
||||||
mkdir -p ~/.gstack/analytics
|
mkdir -p ~/.gstack/analytics
|
||||||
if [ "$_TEL" != "off" ]; then
|
if [ "$_TEL" != "off" ]; then
|
||||||
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
|
echo '{"skill":"review","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(_repo=$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null | tr -cd 'a-zA-Z0-9._-'); echo "${_repo:-unknown}")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
||||||
if [ -f "$_PF" ]; then
|
if [ -f "$_PF" ]; then
|
||||||
|
|
@ -175,7 +175,7 @@ Only run `open` if yes. Always run `touch`.
|
||||||
|
|
||||||
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
||||||
|
|
||||||
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code, file paths, or repo names.
|
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code or file paths. Your repo name is recorded locally only and stripped before any upload.
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
- A) Help gstack get better! (recommended)
|
- A) Help gstack get better! (recommended)
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,7 @@ _QUESTION_TUNING=$(~/.claude/skills/gstack/bin/gstack-config get question_tuning
|
||||||
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
||||||
mkdir -p ~/.gstack/analytics
|
mkdir -p ~/.gstack/analytics
|
||||||
if [ "$_TEL" != "off" ]; then
|
if [ "$_TEL" != "off" ]; then
|
||||||
echo '{"skill":"scrape","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
|
echo '{"skill":"scrape","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(_repo=$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null | tr -cd 'a-zA-Z0-9._-'); echo "${_repo:-unknown}")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
||||||
if [ -f "$_PF" ]; then
|
if [ -f "$_PF" ]; then
|
||||||
|
|
@ -171,7 +171,7 @@ Only run `open` if yes. Always run `touch`.
|
||||||
|
|
||||||
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
||||||
|
|
||||||
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code, file paths, or repo names.
|
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code or file paths. Your repo name is recorded locally only and stripped before any upload.
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
- A) Help gstack get better! (recommended)
|
- A) Help gstack get better! (recommended)
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@ _QUESTION_TUNING=$(${ctx.paths.binDir}/gstack-config get question_tuning 2>/dev/
|
||||||
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
||||||
mkdir -p ~/.gstack/analytics
|
mkdir -p ~/.gstack/analytics
|
||||||
if [ "$_TEL" != "off" ]; then
|
if [ "$_TEL" != "off" ]; then
|
||||||
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
|
echo '{"skill":"${ctx.skillName}","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(_repo=$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null | tr -cd 'a-zA-Z0-9._-'); echo "\${_repo:-unknown}")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
||||||
if [ -f "$_PF" ]; then
|
if [ -f "$_PF" ]; then
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import type { TemplateContext } from '../types';
|
||||||
export function generateTelemetryPrompt(ctx: TemplateContext): string {
|
export function generateTelemetryPrompt(ctx: TemplateContext): string {
|
||||||
return `If \`TEL_PROMPTED\` is \`no\` AND \`LAKE_INTRO\` is \`yes\`: ask telemetry once via AskUserQuestion:
|
return `If \`TEL_PROMPTED\` is \`no\` AND \`LAKE_INTRO\` is \`yes\`: ask telemetry once via AskUserQuestion:
|
||||||
|
|
||||||
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code, file paths, or repo names.
|
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code or file paths. Your repo name is recorded locally only and stripped before any upload.
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
- A) Help gstack get better! (recommended)
|
- A) Help gstack get better! (recommended)
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,7 @@ _QUESTION_TUNING=$(~/.claude/skills/gstack/bin/gstack-config get question_tuning
|
||||||
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
||||||
mkdir -p ~/.gstack/analytics
|
mkdir -p ~/.gstack/analytics
|
||||||
if [ "$_TEL" != "off" ]; then
|
if [ "$_TEL" != "off" ]; then
|
||||||
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
|
echo '{"skill":"setup-browser-cookies","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(_repo=$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null | tr -cd 'a-zA-Z0-9._-'); echo "${_repo:-unknown}")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
||||||
if [ -f "$_PF" ]; then
|
if [ -f "$_PF" ]; then
|
||||||
|
|
@ -167,7 +167,7 @@ Only run `open` if yes. Always run `touch`.
|
||||||
|
|
||||||
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
||||||
|
|
||||||
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code, file paths, or repo names.
|
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code or file paths. Your repo name is recorded locally only and stripped before any upload.
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
- A) Help gstack get better! (recommended)
|
- A) Help gstack get better! (recommended)
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,7 @@ _QUESTION_TUNING=$(~/.claude/skills/gstack/bin/gstack-config get question_tuning
|
||||||
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
||||||
mkdir -p ~/.gstack/analytics
|
mkdir -p ~/.gstack/analytics
|
||||||
if [ "$_TEL" != "off" ]; then
|
if [ "$_TEL" != "off" ]; then
|
||||||
echo '{"skill":"setup-deploy","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
|
echo '{"skill":"setup-deploy","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(_repo=$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null | tr -cd 'a-zA-Z0-9._-'); echo "${_repo:-unknown}")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
||||||
if [ -f "$_PF" ]; then
|
if [ -f "$_PF" ]; then
|
||||||
|
|
@ -174,7 +174,7 @@ Only run `open` if yes. Always run `touch`.
|
||||||
|
|
||||||
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
||||||
|
|
||||||
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code, file paths, or repo names.
|
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code or file paths. Your repo name is recorded locally only and stripped before any upload.
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
- A) Help gstack get better! (recommended)
|
- A) Help gstack get better! (recommended)
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,7 @@ _QUESTION_TUNING=$(~/.claude/skills/gstack/bin/gstack-config get question_tuning
|
||||||
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
||||||
mkdir -p ~/.gstack/analytics
|
mkdir -p ~/.gstack/analytics
|
||||||
if [ "$_TEL" != "off" ]; then
|
if [ "$_TEL" != "off" ]; then
|
||||||
echo '{"skill":"setup-gbrain","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
|
echo '{"skill":"setup-gbrain","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(_repo=$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null | tr -cd 'a-zA-Z0-9._-'); echo "${_repo:-unknown}")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
||||||
if [ -f "$_PF" ]; then
|
if [ -f "$_PF" ]; then
|
||||||
|
|
@ -173,7 +173,7 @@ Only run `open` if yes. Always run `touch`.
|
||||||
|
|
||||||
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
||||||
|
|
||||||
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code, file paths, or repo names.
|
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code or file paths. Your repo name is recorded locally only and stripped before any upload.
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
- A) Help gstack get better! (recommended)
|
- A) Help gstack get better! (recommended)
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,7 @@ _QUESTION_TUNING=$(~/.claude/skills/gstack/bin/gstack-config get question_tuning
|
||||||
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
||||||
mkdir -p ~/.gstack/analytics
|
mkdir -p ~/.gstack/analytics
|
||||||
if [ "$_TEL" != "off" ]; then
|
if [ "$_TEL" != "off" ]; then
|
||||||
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
|
echo '{"skill":"ship","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(_repo=$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null | tr -cd 'a-zA-Z0-9._-'); echo "${_repo:-unknown}")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
||||||
if [ -f "$_PF" ]; then
|
if [ -f "$_PF" ]; then
|
||||||
|
|
@ -175,7 +175,7 @@ Only run `open` if yes. Always run `touch`.
|
||||||
|
|
||||||
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
||||||
|
|
||||||
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code, file paths, or repo names.
|
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code or file paths. Your repo name is recorded locally only and stripped before any upload.
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
- A) Help gstack get better! (recommended)
|
- A) Help gstack get better! (recommended)
|
||||||
|
|
@ -910,7 +910,55 @@ If CEO Review is missing, mention as informational ("CEO Review not run — reco
|
||||||
|
|
||||||
For Design Review: run `source <(~/.claude/skills/gstack/bin/gstack-diff-scope <base> 2>/dev/null)`. If `SCOPE_FRONTEND=true` and no design review (plan-design-review or design-review-lite) exists in the dashboard, mention: "Design Review not run — this PR changes frontend code. The lite design check will run automatically in Step 9, but consider running /design-review for a full visual audit post-implementation." Still never block.
|
For Design Review: run `source <(~/.claude/skills/gstack/bin/gstack-diff-scope <base> 2>/dev/null)`. If `SCOPE_FRONTEND=true` and no design review (plan-design-review or design-review-lite) exists in the dashboard, mention: "Design Review not run — this PR changes frontend code. The lite design check will run automatically in Step 9, but consider running /design-review for a full visual audit post-implementation." Still never block.
|
||||||
|
|
||||||
Continue to Step 2 — do NOT block or ask. Ship runs its own review in Step 9.
|
Continue to Step 1.5 — do NOT block or ask. Ship runs its own review in Step 9.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 1.5: Upstream duplicate audit (pr-prep gate)
|
||||||
|
|
||||||
|
Catches the case where a contributor's branch would file a PR that
|
||||||
|
duplicates an already-open upstream PR or issue. Without this gate
|
||||||
|
the duplicate gets filed and is closed days later, costing reviewer
|
||||||
|
time + contributor goodwill.
|
||||||
|
|
||||||
|
Skip on:
|
||||||
|
- Forks that don't have a tracked upstream remote
|
||||||
|
- Branches where the base is the user's own fork (no upstream to dup)
|
||||||
|
- Explicit `--skip-pr-prep` flag
|
||||||
|
|
||||||
|
Otherwise run `/pr-prep` inline with `GSTACK_FROM_SHIP=1`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Skip if no upstream remote configured (solo-repo case)
|
||||||
|
if ! gh repo view --json nameWithOwner -q .nameWithOwner >/dev/null 2>&1; then
|
||||||
|
echo "[ship] no upstream repo detected, skipping pr-prep audit"
|
||||||
|
else
|
||||||
|
GSTACK_FROM_SHIP=1 ~/.claude/skills/gstack/bin/gstack-skill pr-prep --base "$BASE_BRANCH" --json > /tmp/ship-pr-prep.json 2>&1
|
||||||
|
PR_PREP_EXIT=$?
|
||||||
|
if [ "$PR_PREP_EXIT" -eq 1 ]; then
|
||||||
|
# EXACT_DUP found
|
||||||
|
cat /tmp/ship-pr-prep.json
|
||||||
|
echo ""
|
||||||
|
echo "✗ Ship aborted: pr-prep found exact duplicate upstream work."
|
||||||
|
echo " Resolution paths:"
|
||||||
|
echo " 1. Close your version, comment on the upstream PR with your angle"
|
||||||
|
echo " 2. Cherry-pick unique parts to a new branch + file separately"
|
||||||
|
echo " 3. Override with /ship --skip-pr-prep if coordinated with the upstream PR author"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
# CLEAN / OVERLAP / SIBLING — render summary, continue
|
||||||
|
jq -r '.summary' /tmp/ship-pr-prep.json 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
```
|
||||||
|
|
||||||
|
Note: the JSON report path (`/tmp/ship-pr-prep.json`) is read again in
|
||||||
|
Step 19 (PR body assembly) to surface SIBLING / OVERLAP findings as a
|
||||||
|
collapsed "Upstream context" section in the PR body. SIBLING context
|
||||||
|
helps reviewers triage faster; it does NOT block ship.
|
||||||
|
|
||||||
|
If the pr-prep skill is not installed (older gstack install), fall
|
||||||
|
through with a stderr warn and continue. Don't hard-fail ship on a
|
||||||
|
missing skill.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -97,7 +97,55 @@ If CEO Review is missing, mention as informational ("CEO Review not run — reco
|
||||||
|
|
||||||
For Design Review: run `source <(~/.claude/skills/gstack/bin/gstack-diff-scope <base> 2>/dev/null)`. If `SCOPE_FRONTEND=true` and no design review (plan-design-review or design-review-lite) exists in the dashboard, mention: "Design Review not run — this PR changes frontend code. The lite design check will run automatically in Step 9, but consider running /design-review for a full visual audit post-implementation." Still never block.
|
For Design Review: run `source <(~/.claude/skills/gstack/bin/gstack-diff-scope <base> 2>/dev/null)`. If `SCOPE_FRONTEND=true` and no design review (plan-design-review or design-review-lite) exists in the dashboard, mention: "Design Review not run — this PR changes frontend code. The lite design check will run automatically in Step 9, but consider running /design-review for a full visual audit post-implementation." Still never block.
|
||||||
|
|
||||||
Continue to Step 2 — do NOT block or ask. Ship runs its own review in Step 9.
|
Continue to Step 1.5 — do NOT block or ask. Ship runs its own review in Step 9.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 1.5: Upstream duplicate audit (pr-prep gate)
|
||||||
|
|
||||||
|
Catches the case where a contributor's branch would file a PR that
|
||||||
|
duplicates an already-open upstream PR or issue. Without this gate
|
||||||
|
the duplicate gets filed and is closed days later, costing reviewer
|
||||||
|
time + contributor goodwill.
|
||||||
|
|
||||||
|
Skip on:
|
||||||
|
- Forks that don't have a tracked upstream remote
|
||||||
|
- Branches where the base is the user's own fork (no upstream to dup)
|
||||||
|
- Explicit `--skip-pr-prep` flag
|
||||||
|
|
||||||
|
Otherwise run `/pr-prep` inline with `GSTACK_FROM_SHIP=1`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Skip if no upstream remote configured (solo-repo case)
|
||||||
|
if ! gh repo view --json nameWithOwner -q .nameWithOwner >/dev/null 2>&1; then
|
||||||
|
echo "[ship] no upstream repo detected, skipping pr-prep audit"
|
||||||
|
else
|
||||||
|
GSTACK_FROM_SHIP=1 ~/.claude/skills/gstack/bin/gstack-skill pr-prep --base "$BASE_BRANCH" --json > /tmp/ship-pr-prep.json 2>&1
|
||||||
|
PR_PREP_EXIT=$?
|
||||||
|
if [ "$PR_PREP_EXIT" -eq 1 ]; then
|
||||||
|
# EXACT_DUP found
|
||||||
|
cat /tmp/ship-pr-prep.json
|
||||||
|
echo ""
|
||||||
|
echo "✗ Ship aborted: pr-prep found exact duplicate upstream work."
|
||||||
|
echo " Resolution paths:"
|
||||||
|
echo " 1. Close your version, comment on the upstream PR with your angle"
|
||||||
|
echo " 2. Cherry-pick unique parts to a new branch + file separately"
|
||||||
|
echo " 3. Override with /ship --skip-pr-prep if coordinated with the upstream PR author"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
# CLEAN / OVERLAP / SIBLING — render summary, continue
|
||||||
|
jq -r '.summary' /tmp/ship-pr-prep.json 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
```
|
||||||
|
|
||||||
|
Note: the JSON report path (`/tmp/ship-pr-prep.json`) is read again in
|
||||||
|
Step 19 (PR body assembly) to surface SIBLING / OVERLAP findings as a
|
||||||
|
collapsed "Upstream context" section in the PR body. SIBLING context
|
||||||
|
helps reviewers triage faster; it does NOT block ship.
|
||||||
|
|
||||||
|
If the pr-prep skill is not installed (older gstack install), fall
|
||||||
|
through with a stderr warn and continue. Don't hard-fail ship on a
|
||||||
|
missing skill.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,7 @@ _QUESTION_TUNING=$(~/.claude/skills/gstack/bin/gstack-config get question_tuning
|
||||||
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
||||||
mkdir -p ~/.gstack/analytics
|
mkdir -p ~/.gstack/analytics
|
||||||
if [ "$_TEL" != "off" ]; then
|
if [ "$_TEL" != "off" ]; then
|
||||||
echo '{"skill":"skillify","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true
|
echo '{"skill":"skillify","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(_repo=$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null | tr -cd 'a-zA-Z0-9._-'); echo "${_repo:-unknown}")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
||||||
if [ -f "$_PF" ]; then
|
if [ -f "$_PF" ]; then
|
||||||
|
|
@ -171,7 +171,7 @@ Only run `open` if yes. Always run `touch`.
|
||||||
|
|
||||||
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
||||||
|
|
||||||
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code, file paths, or repo names.
|
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code or file paths. Your repo name is recorded locally only and stripped before any upload.
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
- A) Help gstack get better! (recommended)
|
- A) Help gstack get better! (recommended)
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@ _QUESTION_TUNING=$(~/.claude/skills/gstack/bin/gstack-config get question_tuning
|
||||||
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
||||||
mkdir -p ~/.gstack/analytics
|
mkdir -p ~/.gstack/analytics
|
||||||
if [ "$_TEL" != "off" ]; then
|
if [ "$_TEL" != "off" ]; then
|
||||||
echo '{"skill":"spec","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
|
echo '{"skill":"spec","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(_repo=$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null | tr -cd 'a-zA-Z0-9._-'); echo "${_repo:-unknown}")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
||||||
if [ -f "$_PF" ]; then
|
if [ -f "$_PF" ]; then
|
||||||
|
|
@ -172,7 +172,7 @@ Only run `open` if yes. Always run `touch`.
|
||||||
|
|
||||||
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
||||||
|
|
||||||
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code, file paths, or repo names.
|
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code or file paths. Your repo name is recorded locally only and stripped before any upload.
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
- A) Help gstack get better! (recommended)
|
- A) Help gstack get better! (recommended)
|
||||||
|
|
@ -1073,7 +1073,7 @@ _QUESTION_TUNING=$(~/.claude/skills/gstack/bin/gstack-config get question_tuning
|
||||||
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
||||||
mkdir -p ~/.gstack/analytics
|
mkdir -p ~/.gstack/analytics
|
||||||
if [ "$_TEL" != "off" ]; then
|
if [ "$_TEL" != "off" ]; then
|
||||||
echo '{"skill":"spec","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
|
echo '{"skill":"spec","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(_repo=$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null | tr -cd 'a-zA-Z0-9._-'); echo "${_repo:-unknown}")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
||||||
if [ -f "$_PF" ]; then
|
if [ -f "$_PF" ]; then
|
||||||
|
|
@ -1183,7 +1183,7 @@ Only run `open` if yes. Always run `touch`.
|
||||||
|
|
||||||
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
||||||
|
|
||||||
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code, file paths, or repo names.
|
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code or file paths. Your repo name is recorded locally only and stripped before any upload.
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
- A) Help gstack get better! (recommended)
|
- A) Help gstack get better! (recommended)
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,7 @@ _QUESTION_TUNING=$(~/.claude/skills/gstack/bin/gstack-config get question_tuning
|
||||||
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
||||||
mkdir -p ~/.gstack/analytics
|
mkdir -p ~/.gstack/analytics
|
||||||
if [ "$_TEL" != "off" ]; then
|
if [ "$_TEL" != "off" ]; then
|
||||||
echo '{"skill":"sync-gbrain","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
|
echo '{"skill":"sync-gbrain","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(_repo=$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null | tr -cd 'a-zA-Z0-9._-'); echo "${_repo:-unknown}")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
||||||
if [ -f "$_PF" ]; then
|
if [ -f "$_PF" ]; then
|
||||||
|
|
@ -173,7 +173,7 @@ Only run `open` if yes. Always run `touch`.
|
||||||
|
|
||||||
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
||||||
|
|
||||||
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code, file paths, or repo names.
|
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code or file paths. Your repo name is recorded locally only and stripped before any upload.
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
- A) Help gstack get better! (recommended)
|
- A) Help gstack get better! (recommended)
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,7 @@ _QUESTION_TUNING=$(~/.claude/skills/gstack/bin/gstack-config get question_tuning
|
||||||
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
||||||
mkdir -p ~/.gstack/analytics
|
mkdir -p ~/.gstack/analytics
|
||||||
if [ "$_TEL" != "off" ]; then
|
if [ "$_TEL" != "off" ]; then
|
||||||
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
|
echo '{"skill":"ship","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(_repo=$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null | tr -cd 'a-zA-Z0-9._-'); echo "${_repo:-unknown}")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
||||||
if [ -f "$_PF" ]; then
|
if [ -f "$_PF" ]; then
|
||||||
|
|
@ -175,7 +175,7 @@ Only run `open` if yes. Always run `touch`.
|
||||||
|
|
||||||
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
||||||
|
|
||||||
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code, file paths, or repo names.
|
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code or file paths. Your repo name is recorded locally only and stripped before any upload.
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
- A) Help gstack get better! (recommended)
|
- A) Help gstack get better! (recommended)
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@ _QUESTION_TUNING=$($GSTACK_BIN/gstack-config get question_tuning 2>/dev/null ||
|
||||||
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
||||||
mkdir -p ~/.gstack/analytics
|
mkdir -p ~/.gstack/analytics
|
||||||
if [ "$_TEL" != "off" ]; then
|
if [ "$_TEL" != "off" ]; then
|
||||||
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
|
echo '{"skill":"ship","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(_repo=$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null | tr -cd 'a-zA-Z0-9._-'); echo "${_repo:-unknown}")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
||||||
if [ -f "$_PF" ]; then
|
if [ -f "$_PF" ]; then
|
||||||
|
|
@ -161,7 +161,7 @@ Only run `open` if yes. Always run `touch`.
|
||||||
|
|
||||||
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
||||||
|
|
||||||
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code, file paths, or repo names.
|
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code or file paths. Your repo name is recorded locally only and stripped before any upload.
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
- A) Help gstack get better! (recommended)
|
- A) Help gstack get better! (recommended)
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ _QUESTION_TUNING=$($GSTACK_BIN/gstack-config get question_tuning 2>/dev/null ||
|
||||||
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
echo "QUESTION_TUNING: $_QUESTION_TUNING"
|
||||||
mkdir -p ~/.gstack/analytics
|
mkdir -p ~/.gstack/analytics
|
||||||
if [ "$_TEL" != "off" ]; then
|
if [ "$_TEL" != "off" ]; then
|
||||||
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
|
echo '{"skill":"ship","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(_repo=$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null | tr -cd 'a-zA-Z0-9._-'); echo "${_repo:-unknown}")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do
|
||||||
if [ -f "$_PF" ]; then
|
if [ -f "$_PF" ]; then
|
||||||
|
|
@ -163,7 +163,7 @@ Only run `open` if yes. Always run `touch`.
|
||||||
|
|
||||||
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: ask telemetry once via AskUserQuestion:
|
||||||
|
|
||||||
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code, file paths, or repo names.
|
> Help gstack get better. Share usage data only: skill, duration, crashes, stable device ID. No code or file paths. Your repo name is recorded locally only and stripped before any upload.
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
- A) Help gstack get better! (recommended)
|
- A) Help gstack get better! (recommended)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,65 @@
|
||||||
|
/**
|
||||||
|
* gstack-slug cache-read sanitization.
|
||||||
|
*
|
||||||
|
* `eval "$(gstack-slug)"` is how callers load SLUG/BRANCH. The compute and
|
||||||
|
* fallback paths filter to [a-zA-Z0-9._-], but a value read straight from the
|
||||||
|
* cache file used to be echoed unsanitized — a planted cache file could inject
|
||||||
|
* shell. This pins the fix: a poisoned cache must never produce shell
|
||||||
|
* metacharacters in the SLUG= output line.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { describe, test, expect } from 'bun:test';
|
||||||
|
import { spawnSync } from 'bun';
|
||||||
|
import fs from 'fs';
|
||||||
|
import os from 'os';
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
|
const ROOT = path.resolve(__dirname, '..');
|
||||||
|
const SLUG_BIN = path.join(ROOT, 'bin', 'gstack-slug');
|
||||||
|
|
||||||
|
/** Reproduce the script's cache-key derivation: absolute path with / -> _. */
|
||||||
|
function cacheKeyFor(dir: string): string {
|
||||||
|
return dir.replace(/\//g, '_');
|
||||||
|
}
|
||||||
|
|
||||||
|
function runSlug(cwd: string, home: string) {
|
||||||
|
return spawnSync([SLUG_BIN], {
|
||||||
|
cwd,
|
||||||
|
env: { ...process.env, HOME: home },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('gstack-slug cache-read sanitization', () => {
|
||||||
|
test('a poisoned cache file cannot inject shell metacharacters into output', () => {
|
||||||
|
const home = fs.mkdtempSync(path.join(os.tmpdir(), 'gslug-home-'));
|
||||||
|
const proj = fs.mkdtempSync(path.join(os.tmpdir(), 'gslug-proj-'));
|
||||||
|
try {
|
||||||
|
const cacheDir = path.join(home, '.gstack', 'slug-cache');
|
||||||
|
fs.mkdirSync(cacheDir, { recursive: true });
|
||||||
|
// realpath: macOS tmpdir is a symlink (/var -> /private/var); the script
|
||||||
|
// runs in the resolved cwd, so key off the resolved path.
|
||||||
|
const realProj = fs.realpathSync(proj);
|
||||||
|
const payload = 'evil"; touch ' + path.join(home, 'pwned') + '; echo "x';
|
||||||
|
fs.writeFileSync(path.join(cacheDir, cacheKeyFor(realProj)), payload);
|
||||||
|
|
||||||
|
const out = runSlug(realProj, home);
|
||||||
|
const stdout = out.stdout.toString();
|
||||||
|
|
||||||
|
const slugLine = stdout.split('\n').find((l) => l.startsWith('SLUG='));
|
||||||
|
expect(slugLine).toBeDefined();
|
||||||
|
const slugValue = slugLine!.slice('SLUG='.length);
|
||||||
|
|
||||||
|
// The value must be sanitized: only [a-zA-Z0-9._-], no quotes/semicolons/spaces.
|
||||||
|
expect(slugValue).toMatch(/^[a-zA-Z0-9._-]*$/);
|
||||||
|
expect(slugLine).not.toContain('"');
|
||||||
|
expect(slugLine).not.toContain(';');
|
||||||
|
expect(slugLine).not.toContain(' ');
|
||||||
|
|
||||||
|
// And the injection must not have fired during the script's own run.
|
||||||
|
expect(fs.existsSync(path.join(home, 'pwned'))).toBe(false);
|
||||||
|
} finally {
|
||||||
|
fs.rmSync(home, { recursive: true, force: true });
|
||||||
|
fs.rmSync(proj, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,137 @@
|
||||||
|
/**
|
||||||
|
* Telemetry "no repo identity egress" invariant.
|
||||||
|
*
|
||||||
|
* The telemetry consent copy promises a user's repo name is recorded locally
|
||||||
|
* only and stripped before any upload (scripts/resolvers/preamble/
|
||||||
|
* generate-telemetry-prompt.ts). Two producers write repo/branch identity into
|
||||||
|
* the local skill-usage.jsonl:
|
||||||
|
*
|
||||||
|
* - the preamble epilogue → "repo"
|
||||||
|
* (scripts/resolvers/preamble/generate-preamble-bash.ts)
|
||||||
|
* - gstack-telemetry-log → "_repo_slug", "_branch"
|
||||||
|
* (bin/gstack-telemetry-log)
|
||||||
|
*
|
||||||
|
* gstack-telemetry-sync MUST strip every one of those fields before the remote
|
||||||
|
* POST (bin/gstack-telemetry-sync). This test enforces that contract three ways:
|
||||||
|
*
|
||||||
|
* 1. Coverage — every repo/branch field the producers emit is also stripped.
|
||||||
|
* Catches "added a new repo field, forgot to strip it" (the rename-to-_repo
|
||||||
|
* landmine, or any future producer drift).
|
||||||
|
* 2. Behavior — run the ACTUAL sed strip expressions from the sync script over
|
||||||
|
* a sample event line and assert no repo/branch field survives, while benign
|
||||||
|
* fields do. Catches a broken/edited regex, not just a missing line.
|
||||||
|
* 3. Floor — the three known fields are always in the stripped set, so deleting
|
||||||
|
* a strip rule fails CI even if a producer also stops emitting it.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { describe, test, expect } from 'bun:test';
|
||||||
|
import { spawnSync } from 'bun';
|
||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
|
const ROOT = path.resolve(__dirname, '..');
|
||||||
|
const SYNC = path.join(ROOT, 'bin', 'gstack-telemetry-sync');
|
||||||
|
const PREAMBLE = path.join(ROOT, 'scripts', 'resolvers', 'preamble', 'generate-preamble-bash.ts');
|
||||||
|
const TEL_LOG = path.join(ROOT, 'bin', 'gstack-telemetry-log');
|
||||||
|
|
||||||
|
// Fields that identify the user's repo/branch. The promise is that NONE of
|
||||||
|
// these reach the network. Add to this floor if a new identity field is born.
|
||||||
|
const REPO_IDENTITY_FLOOR = ['repo', '_repo_slug', '_branch'];
|
||||||
|
|
||||||
|
const isRepoIdentity = (field: string) => /repo|branch/i.test(field);
|
||||||
|
|
||||||
|
/** Pull every `sed -e 's/.../g'` expression out of the sync script. */
|
||||||
|
function extractSedExprs(scriptText: string): string[] {
|
||||||
|
return [...scriptText.matchAll(/-e\s+'(s\/[^']*)'/g)].map((m) => m[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The JSON key a strip expression targets, e.g. `,"repo":"[^"]*"` -> `repo`. */
|
||||||
|
function fieldFromSedExpr(expr: string): string | null {
|
||||||
|
const m = expr.match(/,"([A-Za-z_][A-Za-z0-9_]*)":/);
|
||||||
|
return m ? m[1] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Repo/branch JSON keys a producer writes INTO skill-usage.jsonl — the only
|
||||||
|
* file gstack-telemetry-sync reads and uploads. Scoped to the emission lines
|
||||||
|
* that target the synced file so local-only sinks (e.g. the timeline log, which
|
||||||
|
* carries "branch" but is never synced) don't count against the egress invariant.
|
||||||
|
*/
|
||||||
|
function emittedRepoFields(lines: string[]): string[] {
|
||||||
|
const text = lines.join('\n');
|
||||||
|
const keys = [...text.matchAll(/"([A-Za-z_][A-Za-z0-9_]*)":/g)].map((m) => m[1]);
|
||||||
|
return [...new Set(keys.filter(isRepoIdentity))];
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('telemetry no-repo-identity-egress invariant', () => {
|
||||||
|
const syncText = fs.readFileSync(SYNC, 'utf-8');
|
||||||
|
const sedExprs = extractSedExprs(syncText);
|
||||||
|
const strippedRepoExprs = sedExprs.filter((e) => {
|
||||||
|
const f = fieldFromSedExpr(e);
|
||||||
|
return f !== null && isRepoIdentity(f);
|
||||||
|
});
|
||||||
|
const strippedFields = new Set(
|
||||||
|
strippedRepoExprs.map(fieldFromSedExpr).filter((f): f is string => f !== null),
|
||||||
|
);
|
||||||
|
|
||||||
|
test('floor: the three known repo-identity fields are stripped', () => {
|
||||||
|
for (const field of REPO_IDENTITY_FLOOR) {
|
||||||
|
expect(strippedFields.has(field)).toBe(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('coverage: every repo/branch field the producers emit into skill-usage.jsonl is stripped', () => {
|
||||||
|
// Only emission lines that target the synced file (skill-usage.jsonl). The
|
||||||
|
// preamble appends directly; gstack-telemetry-log builds the synced event
|
||||||
|
// with a `printf '{"v":1,...` line into $JSONL_FILE (= skill-usage.jsonl).
|
||||||
|
const preambleSynced = fs
|
||||||
|
.readFileSync(PREAMBLE, 'utf-8')
|
||||||
|
.split('\n')
|
||||||
|
.filter((l) => l.includes('skill-usage.jsonl'));
|
||||||
|
const telLogSynced = fs
|
||||||
|
.readFileSync(TEL_LOG, 'utf-8')
|
||||||
|
.split('\n')
|
||||||
|
.filter((l) => l.includes('"v":1') || l.includes('skill-usage'));
|
||||||
|
const emitted = new Set<string>([
|
||||||
|
...emittedRepoFields(preambleSynced),
|
||||||
|
...emittedRepoFields(telLogSynced),
|
||||||
|
]);
|
||||||
|
// The preamble must emit "repo" — guards against the test silently passing
|
||||||
|
// because a regex stopped matching the producer.
|
||||||
|
expect(emitted.has('repo')).toBe(true);
|
||||||
|
for (const field of emitted) {
|
||||||
|
expect(
|
||||||
|
strippedFields.has(field),
|
||||||
|
`producer emits repo-identity field "${field}" but gstack-telemetry-sync does not strip it (would leak to remote)`,
|
||||||
|
).toBe(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('behavior: the real sed expressions remove repo identity, keep benign fields', () => {
|
||||||
|
const sample =
|
||||||
|
'{"v":1,"ts":"2026-06-02T00:00:00Z","skill":"design-shotgun",' +
|
||||||
|
'"repo":"my-secret-repo","_repo_slug":"acme-my-secret-repo","_branch":"feature-x",' +
|
||||||
|
'"sessions":3,"installation_id":"abc123"}';
|
||||||
|
|
||||||
|
const sedArgs: string[] = [];
|
||||||
|
for (const e of strippedRepoExprs) {
|
||||||
|
sedArgs.push('-e', e);
|
||||||
|
}
|
||||||
|
const out = spawnSync(['sed', ...sedArgs], {
|
||||||
|
stdin: Buffer.from(sample),
|
||||||
|
});
|
||||||
|
const cleaned = out.stdout.toString();
|
||||||
|
|
||||||
|
// No repo/branch identity survives, value or key.
|
||||||
|
expect(cleaned).not.toContain('my-secret-repo');
|
||||||
|
expect(cleaned).not.toContain('feature-x');
|
||||||
|
expect(cleaned).not.toContain('"repo"');
|
||||||
|
expect(cleaned).not.toContain('_repo_slug');
|
||||||
|
expect(cleaned).not.toContain('_branch');
|
||||||
|
|
||||||
|
// Benign fields are untouched — the strip is surgical, not a blanket wipe.
|
||||||
|
expect(cleaned).toContain('"skill":"design-shotgun"');
|
||||||
|
expect(cleaned).toContain('"sessions":3');
|
||||||
|
expect(cleaned).toContain('"ts":"2026-06-02T00:00:00Z"');
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue