mirror of https://github.com/garrytan/gstack.git
Compare commits
3 Commits
9465ac8a68
...
3ae1b9e50c
| Author | SHA1 | Date |
|---|---|---|
|
|
3ae1b9e50c | |
|
|
c43c850cae | |
|
|
5163a538e1 |
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)
|
||||||
|
|
|
||||||
|
|
@ -42,18 +42,10 @@ CLAUDE_BIN=$(command -v claude 2>/dev/null || echo "")
|
||||||
If `NOT_FOUND`, stop and tell the user:
|
If `NOT_FOUND`, stop and tell the user:
|
||||||
"Claude CLI not found. Install Claude Code, then re-run this skill."
|
"Claude CLI not found. Install Claude Code, then re-run this skill."
|
||||||
|
|
||||||
Check auth:
|
Do not preflight Claude authentication by checking credential files. Claude Code
|
||||||
|
may store login state in OS keychain or another host-managed location that is not
|
||||||
```bash
|
visible to this skill. Treat the first `claude -p` invocation as the auth check,
|
||||||
if [ -f "$HOME/.claude/.credentials.json" ] || [ -n "${ANTHROPIC_API_KEY:-}" ]; then
|
then surface any login/auth failure from its JSON or stderr output.
|
||||||
echo "AUTH_FOUND"
|
|
||||||
else
|
|
||||||
echo "AUTH_MISSING"
|
|
||||||
fi
|
|
||||||
```
|
|
||||||
|
|
||||||
If `AUTH_MISSING`, stop and tell the user:
|
|
||||||
"No Claude authentication found. Run `claude` interactively to log in, or export `ANTHROPIC_API_KEY`, then re-run this skill."
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -117,10 +109,14 @@ except Exception as exc:
|
||||||
print(f"CLAUDE_JSON_PARSE_ERROR: {exc}")
|
print(f"CLAUDE_JSON_PARSE_ERROR: {exc}")
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
|
result = obj.get("result") or obj.get("response") or ""
|
||||||
|
result_lower = result.lower()
|
||||||
|
|
||||||
if obj.get("is_error"):
|
if obj.get("is_error"):
|
||||||
print("CLAUDE_ERROR: true")
|
print("CLAUDE_ERROR: true")
|
||||||
|
if any(term in result_lower for term in ("auth", "login", "unauthorized", "not logged in")):
|
||||||
|
print("CLAUDE_AUTH_ERROR: true")
|
||||||
|
|
||||||
result = obj.get("result") or obj.get("response") or ""
|
|
||||||
if result:
|
if result:
|
||||||
print(result)
|
print(result)
|
||||||
|
|
||||||
|
|
@ -137,7 +133,8 @@ if session_id:
|
||||||
PY
|
PY
|
||||||
```
|
```
|
||||||
|
|
||||||
If stderr contains `auth`, `login`, or `unauthorized`, tell the user:
|
If the parsed output contains `CLAUDE_AUTH_ERROR: true`, or stderr contains
|
||||||
|
`auth`, `login`, `unauthorized`, or `not logged in`, tell the user:
|
||||||
"Claude authentication failed. Run `claude` interactively to authenticate or export `ANTHROPIC_API_KEY`."
|
"Claude authentication failed. Run `claude` interactively to authenticate or export `ANTHROPIC_API_KEY`."
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
@ -324,8 +321,7 @@ rm -f "$PROMPT_FILE" "$RESP_FILE" "$ERR_FILE"
|
||||||
## Error Handling
|
## Error Handling
|
||||||
|
|
||||||
- **Binary not found:** Stop with install instructions.
|
- **Binary not found:** Stop with install instructions.
|
||||||
- **Auth missing:** Stop with login/API key instructions.
|
- **Auth failure from Claude output or stderr:** Surface the auth line and ask the user to re-authenticate.
|
||||||
- **Auth failure from stderr:** Surface the stderr line and ask the user to re-authenticate.
|
|
||||||
- **JSON parse failure:** Show raw stdout from `$RESP_FILE` and stderr from `$ERR_FILE`.
|
- **JSON parse failure:** Show raw stdout from `$RESP_FILE` and stderr from `$ERR_FILE`.
|
||||||
- **Empty response:** Tell the user "Claude returned no response. Check stderr for errors."
|
- **Empty response:** Tell the user "Claude returned no response. Check stderr for errors."
|
||||||
- **Resume failure:** Delete `.context/claude-session-id` and retry with a fresh session.
|
- **Resume failure:** Delete `.context/claude-session-id` and retry with a fresh session.
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -1781,6 +1781,8 @@ describe('Codex generation (--host codex)', () => {
|
||||||
expect(content).toContain('--allowedTools Read,Grep,Glob');
|
expect(content).toContain('--allowedTools Read,Grep,Glob');
|
||||||
expect(content).toContain('--disallowedTools Bash,Edit,Write');
|
expect(content).toContain('--disallowedTools Bash,Edit,Write');
|
||||||
expect(content).toContain('is_error');
|
expect(content).toContain('is_error');
|
||||||
|
expect(content).toContain('CLAUDE_AUTH_ERROR');
|
||||||
|
expect(content).not.toContain('.credentials.json');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Codex review step stripped from Codex-host ship and review', () => {
|
test('Codex review step stripped from Codex-host ship and review', () => {
|
||||||
|
|
|
||||||
|
|
@ -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