diff --git a/SKILL.md b/SKILL.md index fa2729051..3a7c77cf9 100644 --- a/SKILL.md +++ b/SKILL.md @@ -37,6 +37,11 @@ echo "SKILL_PREFIX: $_SKILL_PREFIX" source <(~/.claude/skills/gstack/bin/gstack-repo-mode 2>/dev/null) || true REPO_MODE=${REPO_MODE:-unknown} echo "REPO_MODE: $REPO_MODE" +# Auto-migrate legacy ~/.gstack/projects/ data to project-local .gstack/ (once per project) +eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" || true +if [ -n "${PROJECT_DATA_DIR:-}" ] && [ -d "$HOME/.gstack/projects/${SLUG:-}" ] && [ ! -f "${PROJECT_DATA_DIR}/.migrated" ]; then + ~/.claude/skills/gstack/bin/gstack-migrate-local 2>/dev/null && touch "${PROJECT_DATA_DIR}/.migrated" 2>/dev/null || true +fi _LAKE_SEEN=$([ -f ~/.gstack/.completeness-intro-seen ] && echo "yes" || echo "no") echo "LAKE_INTRO: $_LAKE_SEEN" _TEL=$(~/.claude/skills/gstack/bin/gstack-config get telemetry 2>/dev/null || true) diff --git a/autoplan/SKILL.md b/autoplan/SKILL.md index 50c2b30ce..08c4b22aa 100644 --- a/autoplan/SKILL.md +++ b/autoplan/SKILL.md @@ -46,6 +46,11 @@ echo "SKILL_PREFIX: $_SKILL_PREFIX" source <(~/.claude/skills/gstack/bin/gstack-repo-mode 2>/dev/null) || true REPO_MODE=${REPO_MODE:-unknown} echo "REPO_MODE: $REPO_MODE" +# Auto-migrate legacy ~/.gstack/projects/ data to project-local .gstack/ (once per project) +eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" || true +if [ -n "${PROJECT_DATA_DIR:-}" ] && [ -d "$HOME/.gstack/projects/${SLUG:-}" ] && [ ! -f "${PROJECT_DATA_DIR}/.migrated" ]; then + ~/.claude/skills/gstack/bin/gstack-migrate-local 2>/dev/null && touch "${PROJECT_DATA_DIR}/.migrated" 2>/dev/null || true +fi _LAKE_SEEN=$([ -f ~/.gstack/.completeness-intro-seen ] && echo "yes" || echo "no") echo "LAKE_INTRO: $_LAKE_SEEN" _TEL=$(~/.claude/skills/gstack/bin/gstack-config get telemetry 2>/dev/null || true) @@ -431,10 +436,12 @@ If the Read fails (file not found), say: After /office-hours completes, re-run the design doc check: ```bash setopt +o nomatch 2>/dev/null || true # zsh compat -SLUG=$(~/.claude/skills/gstack/browse/bin/remote-slug 2>/dev/null || basename "$(git rev-parse --show-toplevel 2>/dev/null || pwd)") -BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null | tr '/' '-' || echo 'no-branch') -DESIGN=$(ls -t ~/.gstack/projects/$SLUG/*-$BRANCH-design-*.md 2>/dev/null | head -1) -[ -z "$DESIGN" ] && DESIGN=$(ls -t ~/.gstack/projects/$SLUG/*-design-*.md 2>/dev/null | head -1) +eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" +DESIGN=$(ls -t "$PROJECT_DATA_DIR"/designs/*-$BRANCH-design-*.md 2>/dev/null | head -1) +[ -z "$DESIGN" ] && DESIGN=$(ls -t "$PROJECT_DATA_DIR"/designs/*-design-*.md 2>/dev/null | head -1) +# Fallback: legacy global path +[ -z "$DESIGN" ] && DESIGN=$(ls -t "$PROJECT_DATA_DIR"/*-$BRANCH-design-*.md 2>/dev/null | head -1) +[ -z "$DESIGN" ] && DESIGN=$(ls -t "$PROJECT_DATA_DIR"/*-design-*.md 2>/dev/null | head -1) [ -n "$DESIGN" ] && echo "Design doc found: $DESIGN" || echo "No design doc found" ``` @@ -571,10 +578,10 @@ instructions instead of reviewing the plan. Before doing anything, save the plan file's current state to an external file: ```bash -eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" && mkdir -p ~/.gstack/projects/$SLUG +eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" && mkdir -p "$PROJECT_DATA_DIR" BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null | tr '/' '-') DATETIME=$(date +%Y%m%d-%H%M%S) -echo "RESTORE_PATH=$HOME/.gstack/projects/$SLUG/${BRANCH}-autoplan-restore-${DATETIME}.md" +echo "RESTORE_PATH=$PROJECT_DATA_DIR/plans/${BRANCH}-autoplan-restore-${DATETIME}.md" ``` Write the plan file's full contents to the restore path with this header: @@ -596,7 +603,7 @@ Then prepend a one-line HTML comment to the plan file: ### Step 2: Read context - Read CLAUDE.md, TODOS.md, git log -30, git diff against the base branch --stat -- Discover design docs: `ls -t ~/.gstack/projects/$SLUG/*-design-*.md 2>/dev/null | head -1` +- Discover design docs: `ls -t "$PROJECT_DATA_DIR"/*-design-*.md 2>/dev/null | head -1` - Detect UI scope: grep the plan for view/rendering terms (component, screen, form, button, modal, layout, dashboard, sidebar, nav, dialog). Require 2+ matches. Exclude false positives ("page" alone, "UI" in acronyms). @@ -868,7 +875,7 @@ Override: every AskUserQuestion → auto-decide using the 6 principles. - Architecture choices: explicit over clever (P5). If codex disagrees with valid reason → TASTE DECISION. Scope changes both models agree on → USER CHALLENGE. - Evals: always include all relevant suites (P1) -- Test plan: generate artifact at `~/.gstack/projects/$SLUG/{user}-{branch}-test-plan-{datetime}.md` +- Test plan: generate artifact at `"$PROJECT_DATA_DIR"/{user}-{branch}-test-plan-{datetime}.md` - TODOS.md: collect all deferred scope expansions from Phase 1, auto-write **Required execution checklist (Eng):** @@ -972,7 +979,7 @@ produced. Check the plan file and conversation for each item. - [ ] Scope challenge with actual code analysis (not just "scope is fine") - [ ] Architecture ASCII diagram produced - [ ] Test diagram mapping codepaths to test coverage -- [ ] Test plan artifact written to disk at ~/.gstack/projects/$SLUG/ +- [ ] Test plan artifact written to disk at "$PROJECT_DATA_DIR"/ - [ ] "NOT in scope" section written - [ ] "What already exists" section written - [ ] Failure modes registry with critical gap assessment diff --git a/autoplan/SKILL.md.tmpl b/autoplan/SKILL.md.tmpl index 5577b64bc..72a3cf3db 100644 --- a/autoplan/SKILL.md.tmpl +++ b/autoplan/SKILL.md.tmpl @@ -162,7 +162,7 @@ Before doing anything, save the plan file's current state to an external file: {{SLUG_SETUP}} BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null | tr '/' '-') DATETIME=$(date +%Y%m%d-%H%M%S) -echo "RESTORE_PATH=$HOME/.gstack/projects/$SLUG/${BRANCH}-autoplan-restore-${DATETIME}.md" +echo "RESTORE_PATH=$PROJECT_DATA_DIR/plans/${BRANCH}-autoplan-restore-${DATETIME}.md" ``` Write the plan file's full contents to the restore path with this header: @@ -184,7 +184,7 @@ Then prepend a one-line HTML comment to the plan file: ### Step 2: Read context - Read CLAUDE.md, TODOS.md, git log -30, git diff against the base branch --stat -- Discover design docs: `ls -t ~/.gstack/projects/$SLUG/*-design-*.md 2>/dev/null | head -1` +- Discover design docs: `ls -t "$PROJECT_DATA_DIR"/designs/*-design-*.md 2>/dev/null || ls -t ~/.gstack/projects/$SLUG/*-design-*.md 2>/dev/null | head -1` - Detect UI scope: grep the plan for view/rendering terms (component, screen, form, button, modal, layout, dashboard, sidebar, nav, dialog). Require 2+ matches. Exclude false positives ("page" alone, "UI" in acronyms). @@ -456,7 +456,7 @@ Override: every AskUserQuestion → auto-decide using the 6 principles. - Architecture choices: explicit over clever (P5). If codex disagrees with valid reason → TASTE DECISION. Scope changes both models agree on → USER CHALLENGE. - Evals: always include all relevant suites (P1) -- Test plan: generate artifact at `~/.gstack/projects/$SLUG/{user}-{branch}-test-plan-{datetime}.md` +- Test plan: generate artifact at `.gstack/plans/{user}-{branch}-test-plan-{datetime}.md` - TODOS.md: collect all deferred scope expansions from Phase 1, auto-write **Required execution checklist (Eng):** @@ -560,7 +560,7 @@ produced. Check the plan file and conversation for each item. - [ ] Scope challenge with actual code analysis (not just "scope is fine") - [ ] Architecture ASCII diagram produced - [ ] Test diagram mapping codepaths to test coverage -- [ ] Test plan artifact written to disk at ~/.gstack/projects/$SLUG/ +- [ ] Test plan artifact written to disk at .gstack/ - [ ] "NOT in scope" section written - [ ] "What already exists" section written - [ ] Failure modes registry with critical gap assessment diff --git a/benchmark/SKILL.md b/benchmark/SKILL.md index 51e39a100..78106afd4 100644 --- a/benchmark/SKILL.md +++ b/benchmark/SKILL.md @@ -39,6 +39,11 @@ echo "SKILL_PREFIX: $_SKILL_PREFIX" source <(~/.claude/skills/gstack/bin/gstack-repo-mode 2>/dev/null) || true REPO_MODE=${REPO_MODE:-unknown} echo "REPO_MODE: $REPO_MODE" +# Auto-migrate legacy ~/.gstack/projects/ data to project-local .gstack/ (once per project) +eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" || true +if [ -n "${PROJECT_DATA_DIR:-}" ] && [ -d "$HOME/.gstack/projects/${SLUG:-}" ] && [ ! -f "${PROJECT_DATA_DIR}/.migrated" ]; then + ~/.claude/skills/gstack/bin/gstack-migrate-local 2>/dev/null && touch "${PROJECT_DATA_DIR}/.migrated" 2>/dev/null || true +fi _LAKE_SEEN=$([ -f ~/.gstack/.completeness-intro-seen ] && echo "yes" || echo "no") echo "LAKE_INTRO: $_LAKE_SEEN" _TEL=$(~/.claude/skills/gstack/bin/gstack-config get telemetry 2>/dev/null || true) diff --git a/bin/gstack-migrate-local b/bin/gstack-migrate-local new file mode 100755 index 000000000..85552be99 --- /dev/null +++ b/bin/gstack-migrate-local @@ -0,0 +1,198 @@ +#!/usr/bin/env bash +# gstack-migrate-local — migrate + reorganize project data into .gstack/ subdirectories +# +# Handles two migration scenarios: +# 1. Legacy global: ~/.gstack/projects/$SLUG/* → .gstack/{designs,plans,...}/ +# 2. Local flat: .gstack/*-design-*.md → .gstack/designs/ (reorganize in place) +# +# Idempotent — safe to run multiple times. Non-destructive (skips existing files). +# +# Usage: gstack-migrate-local [--dry-run] +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +eval "$("$SCRIPT_DIR/gstack-slug" 2>/dev/null)" + +OLD="$HOME/.gstack/projects/$SLUG" +NEW="$PROJECT_DATA_DIR" +DRY_RUN=false +[ "${1:-}" = "--dry-run" ] && DRY_RUN=true + +MOVED=0 + +migrate() { + local src="$1" dst_dir="$2" + [ -e "$src" ] || return 0 + local name + name=$(basename "$src") + # Skip if source and destination are the same path + local abs_src abs_dst + abs_src=$(cd "$(dirname "$src")" && pwd)/$(basename "$src") + abs_dst="$dst_dir/$name" + [ "$abs_src" = "$abs_dst" ] && return 0 + if [ -e "$dst_dir/$name" ]; then + # Target exists — remove source duplicate if it's a file (dedup) + if [ -f "$src" ] && [ -f "$dst_dir/$name" ]; then + $DRY_RUN || rm -f "$src" + fi + return 0 + fi + if $DRY_RUN; then + echo " WOULD MOVE: $name → $dst_dir/" + else + mkdir -p "$dst_dir" + mv "$src" "$dst_dir/" + fi + MOVED=$((MOVED + 1)) +} + +migrate_dir() { + local src="$1" dst="$2" + [ -d "$src" ] || return 0 + if [ -d "$dst" ]; then + # Merge contents + for f in "$src"/*; do + [ -e "$f" ] && migrate "$f" "$dst" + done + $DRY_RUN || rmdir "$src" 2>/dev/null || true + else + if $DRY_RUN; then + echo " WOULD MOVE: $(basename "$src")/ → $(dirname "$dst")/" + else + mkdir -p "$(dirname "$dst")" + mv "$src" "$dst" + fi + MOVED=$((MOVED + 1)) + fi +} + +# ═══════════════════════════════════════════════════════════ +# Phase 1: Migrate from legacy global ~/.gstack/projects/$SLUG/ +# ═══════════════════════════════════════════════════════════ + +if [ -d "$OLD" ]; then + # Design docs + for f in "$OLD"/*-design-*.md "$OLD"/*-design-audit-*.md; do + [ -e "$f" ] && migrate "$f" "$NEW/designs" + done + + # Test plans, autoplan, eng review + for f in "$OLD"/*-test-plan-*.md "$OLD"/*-eng-review-*.md "$OLD"/*-autoplan-restore-*.md "$OLD"/*-ship-test-plan-*.md; do + [ -e "$f" ] && migrate "$f" "$NEW/plans" + done + + # CEO plans directory + [ -d "$OLD/ceo-plans" ] && migrate_dir "$OLD/ceo-plans" "$NEW/plans/ceo-plans" + + # Review logs + for f in "$OLD"/*-reviews.jsonl; do + [ -e "$f" ] && migrate "$f" "$NEW" + done + + # Test outcomes + for f in "$OLD"/*-test-outcome-*.md; do + [ -e "$f" ] && migrate "$f" "$NEW" + done + + # Evals directory + [ -d "$OLD/evals" ] && migrate_dir "$OLD/evals" "$NEW/evals" + + # Greptile history + [ -e "$OLD/greptile-history.md" ] && migrate "$OLD/greptile-history.md" "$NEW" + + # Deploy confirmation fingerprint + [ -e "$OLD/land-deploy-confirmed" ] && migrate "$OLD/land-deploy-confirmed" "$NEW" + + # CEO handoff files + for f in "$OLD"/*-ceo-handoff-*.md; do + [ -e "$f" ] && migrate "$f" "$NEW" + done + + # repo-mode.json → .gstack/local/ (machine-local cache) + [ -e "$OLD/repo-mode.json" ] && migrate "$OLD/repo-mode.json" "$NEW/local" + + # Clean up empty legacy directory + $DRY_RUN || rmdir "$OLD" 2>/dev/null || true +fi + +# ═══════════════════════════════════════════════════════════ +# Phase 2: Reorganize flat files in .gstack/ root → subdirectories +# ═══════════════════════════════════════════════════════════ + +if [ -d "$NEW" ]; then + # Design docs at root → designs/ + for f in "$NEW"/*-design-*.md "$NEW"/*-design-audit-*.md "$NEW"/*-architecture*.md; do + [ -e "$f" ] && migrate "$f" "$NEW/designs" + done + + # Test plans at root → plans/ + for f in "$NEW"/*-test-plan-*.md "$NEW"/*-eng-review-*.md "$NEW"/*-autoplan-restore-*.md "$NEW"/*-ship-test-plan-*.md; do + [ -e "$f" ] && migrate "$f" "$NEW/plans" + done + + # Implementation plans / roadmaps at root → plans/ + for f in "$NEW"/*-implementation-plan*.md "$NEW"/*-delivery-roadmap*.md; do + [ -e "$f" ] && migrate "$f" "$NEW/plans" + done + + # CEO plans at root → plans/ceo-plans/ + [ -d "$NEW/ceo-plans" ] && [ "$NEW/ceo-plans" != "$NEW/plans/ceo-plans" ] && \ + migrate_dir "$NEW/ceo-plans" "$NEW/plans/ceo-plans" + + # repo-mode.json at root → local/ + [ -e "$NEW/repo-mode.json" ] && migrate "$NEW/repo-mode.json" "$NEW/local" + + # browse.json at root → local/ (old browse daemon location) + [ -e "$NEW/browse.json" ] && migrate "$NEW/browse.json" "$NEW/local" + for f in "$NEW"/browse-console.log "$NEW"/browse-network.log "$NEW"/browse-dialog.log "$NEW"/browse-server.log; do + [ -e "$f" ] && migrate "$f" "$NEW/local" + done +fi + +# ═══════════════════════════════════════════════════════════ +# Phase 3: Ensure .gitignore and project .gitignore are correct +# ═══════════════════════════════════════════════════════════ + +if ! $DRY_RUN; then + mkdir -p "$NEW" + + # Ensure .gstack/.gitignore has local/ and .migrated + GITIGNORE="$NEW/.gitignore" + CONTENT="" + [ -f "$GITIGNORE" ] && CONTENT=$(cat "$GITIGNORE") + UPDATED=false + if ! echo "$CONTENT" | grep -q '^local/$' 2>/dev/null; then + CONTENT="${CONTENT:+$CONTENT +}local/" + UPDATED=true + fi + if ! echo "$CONTENT" | grep -q '^\.migrated$' 2>/dev/null; then + CONTENT="${CONTENT:+$CONTENT +}.migrated" + UPDATED=true + fi + $UPDATED && echo "$CONTENT" > "$GITIGNORE" + + # Remove blanket .gstack/ from project .gitignore + GIT_ROOT=$(git rev-parse --show-toplevel 2>/dev/null || echo "") + if [ -n "$GIT_ROOT" ] && [ -f "$GIT_ROOT/.gitignore" ]; then + if grep -q '^\.gstack/\?$' "$GIT_ROOT/.gitignore" 2>/dev/null; then + sed -i.bak '/^\.gstack\/\?$/d' "$GIT_ROOT/.gitignore" + rm -f "$GIT_ROOT/.gitignore.bak" + fi + fi +fi + +# ═══════════════════════════════════════════════════════════ +# Summary +# ═══════════════════════════════════════════════════════════ + +if [ "$MOVED" -eq 0 ]; then + exit 0 +fi + +if $DRY_RUN; then + echo "Dry run: $MOVED items would be migrated. Run without --dry-run to execute." +else + echo "gstack: migrated $MOVED items to $NEW" +fi diff --git a/bin/gstack-repo-mode b/bin/gstack-repo-mode index 0b4d6da64..c6ee94a65 100755 --- a/bin/gstack-repo-mode +++ b/bin/gstack-repo-mode @@ -34,7 +34,12 @@ if [ -n "$OVERRIDE" ] && [ "$OVERRIDE" != "null" ]; then fi # Check cache (7-day TTL) -CACHE_DIR="$HOME/.gstack/projects/$SLUG" +GIT_ROOT=$(git rev-parse --show-toplevel 2>/dev/null || echo "") +if [ -n "$GIT_ROOT" ]; then + CACHE_DIR="$GIT_ROOT/.gstack/local" +else + CACHE_DIR="$HOME/.gstack/projects/$SLUG" +fi CACHE_FILE="$CACHE_DIR/repo-mode.json" if [ -f "$CACHE_FILE" ]; then CACHE_AGE=$(( $(date +%s) - $(stat -f %m "$CACHE_FILE" 2>/dev/null || stat -c %Y "$CACHE_FILE" 2>/dev/null || echo 0) )) diff --git a/bin/gstack-review-log b/bin/gstack-review-log index 62c9e1719..8c7250d24 100755 --- a/bin/gstack-review-log +++ b/bin/gstack-review-log @@ -4,8 +4,7 @@ set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" eval "$("$SCRIPT_DIR/gstack-slug" 2>/dev/null)" -GSTACK_HOME="${GSTACK_HOME:-$HOME/.gstack}" -mkdir -p "$GSTACK_HOME/projects/$SLUG" +mkdir -p "$PROJECT_DATA_DIR" # Validate: input must be parseable JSON (reject malformed or injection attempts) INPUT="$1" @@ -15,4 +14,4 @@ if ! printf '%s' "$INPUT" | bun -e "JSON.parse(await Bun.stdin.text())" 2>/dev/n exit 1 fi -echo "$INPUT" >> "$GSTACK_HOME/projects/$SLUG/$BRANCH-reviews.jsonl" +echo "$INPUT" >> "$PROJECT_DATA_DIR/$BRANCH-reviews.jsonl" diff --git a/bin/gstack-review-read b/bin/gstack-review-read index ccf1d70f6..cb15040a9 100755 --- a/bin/gstack-review-read +++ b/bin/gstack-review-read @@ -4,8 +4,14 @@ set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" eval "$("$SCRIPT_DIR/gstack-slug" 2>/dev/null)" -GSTACK_HOME="${GSTACK_HOME:-$HOME/.gstack}" -cat "$GSTACK_HOME/projects/$SLUG/$BRANCH-reviews.jsonl" 2>/dev/null || echo "NO_REVIEWS" +# Try project-local first, fall back to legacy global path +if [ -f "$PROJECT_DATA_DIR/$BRANCH-reviews.jsonl" ]; then + cat "$PROJECT_DATA_DIR/$BRANCH-reviews.jsonl" +elif [ -f "$HOME/.gstack/projects/$SLUG/$BRANCH-reviews.jsonl" ]; then + cat "$HOME/.gstack/projects/$SLUG/$BRANCH-reviews.jsonl" +else + echo "NO_REVIEWS" +fi echo "---CONFIG---" "$SCRIPT_DIR/gstack-config" get skip_eng_review 2>/dev/null || echo "false" echo "---HEAD---" diff --git a/bin/gstack-slug b/bin/gstack-slug index baa1403f3..04e8f928b 100755 --- a/bin/gstack-slug +++ b/bin/gstack-slug @@ -14,5 +14,12 @@ BRANCH=$(printf '%s' "${RAW_BRANCH:-}" | tr -cd 'a-zA-Z0-9._-') # Fallback when git context is absent SLUG="${SLUG:-$(basename "$PWD" | tr -cd 'a-zA-Z0-9._-')}" BRANCH="${BRANCH:-unknown}" +GIT_ROOT=$(git rev-parse --show-toplevel 2>/dev/null || echo "") +if [ -n "$GIT_ROOT" ]; then + PROJECT_DATA_DIR="$GIT_ROOT/.gstack" +else + PROJECT_DATA_DIR="$HOME/.gstack/projects/$SLUG" +fi echo "SLUG=$SLUG" echo "BRANCH=$BRANCH" +echo "PROJECT_DATA_DIR=$PROJECT_DATA_DIR" diff --git a/browse/SKILL.md b/browse/SKILL.md index a9f95ec2c..a653e699d 100644 --- a/browse/SKILL.md +++ b/browse/SKILL.md @@ -39,6 +39,11 @@ echo "SKILL_PREFIX: $_SKILL_PREFIX" source <(~/.claude/skills/gstack/bin/gstack-repo-mode 2>/dev/null) || true REPO_MODE=${REPO_MODE:-unknown} echo "REPO_MODE: $REPO_MODE" +# Auto-migrate legacy ~/.gstack/projects/ data to project-local .gstack/ (once per project) +eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" || true +if [ -n "${PROJECT_DATA_DIR:-}" ] && [ -d "$HOME/.gstack/projects/${SLUG:-}" ] && [ ! -f "${PROJECT_DATA_DIR}/.migrated" ]; then + ~/.claude/skills/gstack/bin/gstack-migrate-local 2>/dev/null && touch "${PROJECT_DATA_DIR}/.migrated" 2>/dev/null || true +fi _LAKE_SEEN=$([ -f ~/.gstack/.completeness-intro-seen ] && echo "yes" || echo "no") echo "LAKE_INTRO: $_LAKE_SEEN" _TEL=$(~/.claude/skills/gstack/bin/gstack-config get telemetry 2>/dev/null || true) diff --git a/browse/src/config.ts b/browse/src/config.ts index 04f166433..7258458be 100644 --- a/browse/src/config.ts +++ b/browse/src/config.ts @@ -59,7 +59,7 @@ export function resolveConfig( projectDir = path.dirname(stateDir); // parent of .gstack/ } else { projectDir = getGitRoot() || process.cwd(); - stateDir = path.join(projectDir, '.gstack'); + stateDir = path.join(projectDir, '.gstack', 'local'); stateFile = path.join(stateDir, 'browse.json'); } @@ -90,26 +90,34 @@ export function ensureStateDir(config: BrowseConfig): void { throw err; } - // Ensure .gstack/ is in the project's .gitignore - const gitignorePath = path.join(config.projectDir, '.gitignore'); + // Ensure .gstack/.gitignore exists with "local/" (machine-local state) + const gstackDir = path.resolve(config.stateDir, '..'); + const innerGitignore = path.join(gstackDir, '.gitignore'); try { - const content = fs.readFileSync(gitignorePath, 'utf-8'); - if (!content.match(/^\.gstack\/?$/m)) { - const separator = content.endsWith('\n') ? '' : '\n'; - fs.appendFileSync(gitignorePath, `${separator}.gstack/\n`); + let content = ''; + try { content = fs.readFileSync(innerGitignore, 'utf-8'); } catch { /* new file */ } + let updated = false; + if (!content.match(/^local\/?$/m)) { + const separator = content.length > 0 && !content.endsWith('\n') ? '\n' : ''; + content = content + separator + 'local/\n'; + updated = true; } - } catch (err: any) { - if (err.code !== 'ENOENT') { - // Write warning to server log (visible even in daemon mode) - const logPath = path.join(config.stateDir, 'browse-server.log'); - try { - fs.appendFileSync(logPath, `[${new Date().toISOString()}] Warning: could not update .gitignore at ${gitignorePath}: ${err.message}\n`); - } catch { - // stateDir write failed too — nothing more we can do - } + if (!content.match(/^\.migrated$/m)) { + content = content + '.migrated\n'; + updated = true; } - // ENOENT (no .gitignore) — skip silently - } + if (updated) fs.writeFileSync(innerGitignore, content); + } catch { /* non-fatal */ } + + // Migration: remove blanket .gstack/ from project .gitignore (if present) + const projectGitignore = path.join(config.projectDir, '.gitignore'); + try { + const content = fs.readFileSync(projectGitignore, 'utf-8'); + if (content.match(/^\.gstack\/?$/m)) { + const newContent = content.replace(/^\.gstack\/?$/m, '').replace(/\n{3,}/g, '\n\n'); + fs.writeFileSync(projectGitignore, newContent); + } + } catch { /* non-fatal — file may not exist or may be unwritable */ } } /** diff --git a/canary/SKILL.md b/canary/SKILL.md index ed814098b..c9ee10853 100644 --- a/canary/SKILL.md +++ b/canary/SKILL.md @@ -39,6 +39,11 @@ echo "SKILL_PREFIX: $_SKILL_PREFIX" source <(~/.claude/skills/gstack/bin/gstack-repo-mode 2>/dev/null) || true REPO_MODE=${REPO_MODE:-unknown} echo "REPO_MODE: $REPO_MODE" +# Auto-migrate legacy ~/.gstack/projects/ data to project-local .gstack/ (once per project) +eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" || true +if [ -n "${PROJECT_DATA_DIR:-}" ] && [ -d "$HOME/.gstack/projects/${SLUG:-}" ] && [ ! -f "${PROJECT_DATA_DIR}/.migrated" ]; then + ~/.claude/skills/gstack/bin/gstack-migrate-local 2>/dev/null && touch "${PROJECT_DATA_DIR}/.migrated" 2>/dev/null || true +fi _LAKE_SEEN=$([ -f ~/.gstack/.completeness-intro-seen ] && echo "yes" || echo "no") echo "LAKE_INTRO: $_LAKE_SEEN" _TEL=$(~/.claude/skills/gstack/bin/gstack-config get telemetry 2>/dev/null || true) @@ -560,7 +565,7 @@ Log the result for the review dashboard: ```bash eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" -mkdir -p ~/.gstack/projects/$SLUG +mkdir -p "$PROJECT_DATA_DIR" ``` Write a JSONL entry: `{"skill":"canary","timestamp":"","status":"","url":"","duration_min":,"alerts":}` diff --git a/canary/SKILL.md.tmpl b/canary/SKILL.md.tmpl index 680b58147..49be1df67 100644 --- a/canary/SKILL.md.tmpl +++ b/canary/SKILL.md.tmpl @@ -194,7 +194,7 @@ Log the result for the review dashboard: ```bash {{SLUG_EVAL}} -mkdir -p ~/.gstack/projects/$SLUG +mkdir -p "$PROJECT_DATA_DIR" ``` Write a JSONL entry: `{"skill":"canary","timestamp":"","status":"","url":"","duration_min":,"alerts":}` diff --git a/codex/SKILL.md b/codex/SKILL.md index 380382ff6..0a655734b 100644 --- a/codex/SKILL.md +++ b/codex/SKILL.md @@ -40,6 +40,11 @@ echo "SKILL_PREFIX: $_SKILL_PREFIX" source <(~/.claude/skills/gstack/bin/gstack-repo-mode 2>/dev/null) || true REPO_MODE=${REPO_MODE:-unknown} echo "REPO_MODE: $REPO_MODE" +# Auto-migrate legacy ~/.gstack/projects/ data to project-local .gstack/ (once per project) +eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" || true +if [ -n "${PROJECT_DATA_DIR:-}" ] && [ -d "$HOME/.gstack/projects/${SLUG:-}" ] && [ ! -f "${PROJECT_DATA_DIR}/.migrated" ]; then + ~/.claude/skills/gstack/bin/gstack-migrate-local 2>/dev/null && touch "${PROJECT_DATA_DIR}/.migrated" 2>/dev/null || true +fi _LAKE_SEEN=$([ -f ~/.gstack/.completeness-intro-seen ] && echo "yes" || echo "no") echo "LAKE_INTRO: $_LAKE_SEEN" _TEL=$(~/.claude/skills/gstack/bin/gstack-config get telemetry 2>/dev/null || true) diff --git a/connect-chrome/SKILL.md b/connect-chrome/SKILL.md index 57826bbde..7c47f9cc1 100644 --- a/connect-chrome/SKILL.md +++ b/connect-chrome/SKILL.md @@ -37,6 +37,11 @@ echo "SKILL_PREFIX: $_SKILL_PREFIX" source <(~/.claude/skills/gstack/bin/gstack-repo-mode 2>/dev/null) || true REPO_MODE=${REPO_MODE:-unknown} echo "REPO_MODE: $REPO_MODE" +# Auto-migrate legacy ~/.gstack/projects/ data to project-local .gstack/ (once per project) +eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" || true +if [ -n "${PROJECT_DATA_DIR:-}" ] && [ -d "$HOME/.gstack/projects/${SLUG:-}" ] && [ ! -f "${PROJECT_DATA_DIR}/.migrated" ]; then + ~/.claude/skills/gstack/bin/gstack-migrate-local 2>/dev/null && touch "${PROJECT_DATA_DIR}/.migrated" 2>/dev/null || true +fi _LAKE_SEEN=$([ -f ~/.gstack/.completeness-intro-seen ] && echo "yes" || echo "no") echo "LAKE_INTRO: $_LAKE_SEEN" _TEL=$(~/.claude/skills/gstack/bin/gstack-config get telemetry 2>/dev/null || true) diff --git a/cso/SKILL.md b/cso/SKILL.md index 5e448639b..90a41b004 100644 --- a/cso/SKILL.md +++ b/cso/SKILL.md @@ -43,6 +43,11 @@ echo "SKILL_PREFIX: $_SKILL_PREFIX" source <(~/.claude/skills/gstack/bin/gstack-repo-mode 2>/dev/null) || true REPO_MODE=${REPO_MODE:-unknown} echo "REPO_MODE: $REPO_MODE" +# Auto-migrate legacy ~/.gstack/projects/ data to project-local .gstack/ (once per project) +eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" || true +if [ -n "${PROJECT_DATA_DIR:-}" ] && [ -d "$HOME/.gstack/projects/${SLUG:-}" ] && [ ! -f "${PROJECT_DATA_DIR}/.migrated" ]; then + ~/.claude/skills/gstack/bin/gstack-migrate-local 2>/dev/null && touch "${PROJECT_DATA_DIR}/.migrated" 2>/dev/null || true +fi _LAKE_SEEN=$([ -f ~/.gstack/.completeness-intro-seen ] && echo "yes" || echo "no") echo "LAKE_INTRO: $_LAKE_SEEN" _TEL=$(~/.claude/skills/gstack/bin/gstack-config get telemetry 2>/dev/null || true) diff --git a/design-consultation/SKILL.md b/design-consultation/SKILL.md index 86971887e..c05e626ab 100644 --- a/design-consultation/SKILL.md +++ b/design-consultation/SKILL.md @@ -44,6 +44,11 @@ echo "SKILL_PREFIX: $_SKILL_PREFIX" source <(~/.claude/skills/gstack/bin/gstack-repo-mode 2>/dev/null) || true REPO_MODE=${REPO_MODE:-unknown} echo "REPO_MODE: $REPO_MODE" +# Auto-migrate legacy ~/.gstack/projects/ data to project-local .gstack/ (once per project) +eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" || true +if [ -n "${PROJECT_DATA_DIR:-}" ] && [ -d "$HOME/.gstack/projects/${SLUG:-}" ] && [ ! -f "${PROJECT_DATA_DIR}/.migrated" ]; then + ~/.claude/skills/gstack/bin/gstack-migrate-local 2>/dev/null && touch "${PROJECT_DATA_DIR}/.migrated" 2>/dev/null || true +fi _LAKE_SEEN=$([ -f ~/.gstack/.completeness-intro-seen ] && echo "yes" || echo "no") echo "LAKE_INTRO: $_LAKE_SEEN" _TEL=$(~/.claude/skills/gstack/bin/gstack-config get telemetry 2>/dev/null || true) @@ -380,7 +385,7 @@ Look for office-hours output: ```bash setopt +o nomatch 2>/dev/null || true # zsh compat eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" -ls ~/.gstack/projects/$SLUG/*office-hours* 2>/dev/null | head -5 +ls "$PROJECT_DATA_DIR/"*office-hours* 2>/dev/null || ls ~/.gstack/projects/$SLUG/*office-hours* 2>/dev/null | head -5 ls .context/*office-hours* .context/attachments/*office-hours* 2>/dev/null | head -5 ``` diff --git a/design-consultation/SKILL.md.tmpl b/design-consultation/SKILL.md.tmpl index 2ce7c1d3b..52f4aad08 100644 --- a/design-consultation/SKILL.md.tmpl +++ b/design-consultation/SKILL.md.tmpl @@ -55,7 +55,7 @@ Look for office-hours output: ```bash setopt +o nomatch 2>/dev/null || true # zsh compat {{SLUG_EVAL}} -ls ~/.gstack/projects/$SLUG/*office-hours* 2>/dev/null | head -5 +ls "$PROJECT_DATA_DIR/"*office-hours* 2>/dev/null || ls ~/.gstack/projects/$SLUG/*office-hours* 2>/dev/null | head -5 ls .context/*office-hours* .context/attachments/*office-hours* 2>/dev/null | head -5 ``` diff --git a/design-review/SKILL.md b/design-review/SKILL.md index fb0824422..cb895c8ff 100644 --- a/design-review/SKILL.md +++ b/design-review/SKILL.md @@ -44,6 +44,11 @@ echo "SKILL_PREFIX: $_SKILL_PREFIX" source <(~/.claude/skills/gstack/bin/gstack-repo-mode 2>/dev/null) || true REPO_MODE=${REPO_MODE:-unknown} echo "REPO_MODE: $REPO_MODE" +# Auto-migrate legacy ~/.gstack/projects/ data to project-local .gstack/ (once per project) +eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" || true +if [ -n "${PROJECT_DATA_DIR:-}" ] && [ -d "$HOME/.gstack/projects/${SLUG:-}" ] && [ ! -f "${PROJECT_DATA_DIR}/.migrated" ]; then + ~/.claude/skills/gstack/bin/gstack-migrate-local 2>/dev/null && touch "${PROJECT_DATA_DIR}/.migrated" 2>/dev/null || true +fi _LAKE_SEEN=$([ -f ~/.gstack/.completeness-intro-seen ] && echo "yes" || echo "no") echo "LAKE_INTRO: $_LAKE_SEEN" _TEL=$(~/.claude/skills/gstack/bin/gstack-config get telemetry 2>/dev/null || true) @@ -618,7 +623,7 @@ Commands: - `$D iterate --session /path/session.json --feedback "..." --output /path.png` — iterate **CRITICAL PATH RULE:** All design artifacts (mockups, comparison boards, approved.json) -MUST be saved to `~/.gstack/projects/$SLUG/designs/`, NEVER to `.context/`, +MUST be saved to `"$PROJECT_DATA_DIR"/designs/`, NEVER to `.context/`, `docs/designs/`, `/tmp/`, or any project-local directory. Design artifacts are USER data, not project files. They persist across branches, conversations, and workspaces. @@ -630,7 +635,7 @@ If `DESIGN_NOT_AVAILABLE`: skip mockup generation — the fix loop works without ```bash eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" -REPORT_DIR=~/.gstack/projects/$SLUG/designs/design-audit-$(date +%Y%m%d) +REPORT_DIR="$PROJECT_DATA_DIR"/designs/design-audit-$(date +%Y%m%d) mkdir -p "$REPORT_DIR/screenshots" echo "REPORT_DIR: $REPORT_DIR" ``` @@ -888,9 +893,9 @@ Compare screenshots and observations across pages for: **Project-scoped:** ```bash -eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" && mkdir -p ~/.gstack/projects/$SLUG +eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" && mkdir -p "$PROJECT_DATA_DIR" ``` -Write to: `~/.gstack/projects/{slug}/{user}-{branch}-design-audit-{datetime}.md` +Write to: `.gstack/{user}-{branch}-design-audit-{datetime}.md` **Baseline:** Write `design-baseline.json` for regression mode: ```json @@ -1046,7 +1051,7 @@ Record baseline design score and AI slop score at end of Phase 6. ## Output Structure ``` -~/.gstack/projects/$SLUG/designs/design-audit-{YYYYMMDD}/ +"$PROJECT_DATA_DIR"/designs/design-audit-{YYYYMMDD}/ ├── design-audit-{domain}.md # Structured report ├── screenshots/ │ ├── first-impression.png # Phase 1 @@ -1272,9 +1277,9 @@ Write the report to `$REPORT_DIR` (already set up in the setup phase): **Also write a summary to the project index:** ```bash -eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" && mkdir -p ~/.gstack/projects/$SLUG +eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" && mkdir -p "$PROJECT_DATA_DIR" ``` -Write a one-line summary to `~/.gstack/projects/{slug}/{user}-{branch}-design-audit-{datetime}.md` with a pointer to the full report in `$REPORT_DIR`. +Write a one-line summary to `.gstack/{user}-{branch}-design-audit-{datetime}.md` with a pointer to the full report in `$REPORT_DIR`. **Per-finding additions** (beyond standard design audit report): - Fix Status: verified / best-effort / reverted / deferred diff --git a/design-review/SKILL.md.tmpl b/design-review/SKILL.md.tmpl index 904a732c4..7775b9d64 100644 --- a/design-review/SKILL.md.tmpl +++ b/design-review/SKILL.md.tmpl @@ -90,7 +90,7 @@ If `DESIGN_NOT_AVAILABLE`: skip mockup generation — the fix loop works without ```bash eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" -REPORT_DIR=~/.gstack/projects/$SLUG/designs/design-audit-$(date +%Y%m%d) +REPORT_DIR="$PROJECT_DATA_DIR"/designs/design-audit-$(date +%Y%m%d) mkdir -p "$REPORT_DIR/screenshots" echo "REPORT_DIR: $REPORT_DIR" ``` @@ -110,7 +110,7 @@ Record baseline design score and AI slop score at end of Phase 6. ## Output Structure ``` -~/.gstack/projects/$SLUG/designs/design-audit-{YYYYMMDD}/ +"$PROJECT_DATA_DIR"/designs/design-audit-{YYYYMMDD}/ ├── design-audit-{domain}.md # Structured report ├── screenshots/ │ ├── first-impression.png # Phase 1 @@ -258,7 +258,7 @@ Write the report to `$REPORT_DIR` (already set up in the setup phase): ```bash {{SLUG_SETUP}} ``` -Write a one-line summary to `~/.gstack/projects/{slug}/{user}-{branch}-design-audit-{datetime}.md` with a pointer to the full report in `$REPORT_DIR`. +Write a one-line summary to `.gstack/{user}-{branch}-design-audit-{datetime}.md` with a pointer to the full report in `$REPORT_DIR`. **Per-finding additions** (beyond standard design audit report): - Fix Status: verified / best-effort / reverted / deferred diff --git a/docs/skills.md b/docs/skills.md index ae6ddd688..37b143bba 100644 --- a/docs/skills.md +++ b/docs/skills.md @@ -84,7 +84,7 @@ Recommends A because you learn from real usage. CRM data comes naturally in week ### The design doc -Both modes end with a design doc written to `~/.gstack/projects/` — and that doc feeds directly into `/plan-ceo-review` and `/plan-eng-review`. The full lifecycle is now: `office-hours → plan → implement → review → QA → ship → retro`. +Both modes end with a design doc written to `.gstack/` — and that doc feeds directly into `/plan-ceo-review` and `/plan-eng-review`. The full lifecycle is now: `office-hours → plan → implement → review → QA → ship → retro`. After the design doc is approved, `/office-hours` reflects on what it noticed about how you think — not generic praise, but specific callbacks to things you said during the session. The observations appear in the design doc too, so you re-encounter them when you re-read later. @@ -138,7 +138,7 @@ It asks, **"what is the 10-star product hiding inside this request?"** - **HOLD SCOPE** — maximum rigor on the existing plan. No expansions surfaced. - **SCOPE REDUCTION** — find the minimum viable version. Cut everything else. -Visions and decisions are persisted to `~/.gstack/projects/` so they survive beyond the conversation. Exceptional visions can be promoted to `docs/designs/` in your repo for the team. +Visions and decisions are persisted to `.gstack/` so they survive beyond the conversation. Exceptional visions can be promoted to `docs/designs/` in your repo for the team. --- @@ -219,7 +219,7 @@ Eng Review is the only required gate (disable with `gstack-config set skip_eng_r ### Plan-to-QA flow -When `/plan-eng-review` finishes the test review section, it writes a test plan artifact to `~/.gstack/projects/`. When you later run `/qa`, it picks up that test plan automatically — your engineering review feeds directly into QA testing with no manual copy-paste. +When `/plan-eng-review` finishes the test review section, it writes a test plan artifact to `.gstack/`. When you later run `/qa`, it picks up that test plan automatically — your engineering review feeds directly into QA testing with no manual copy-paste. --- diff --git a/document-release/SKILL.md b/document-release/SKILL.md index 2758f0cde..203088e1e 100644 --- a/document-release/SKILL.md +++ b/document-release/SKILL.md @@ -41,6 +41,11 @@ echo "SKILL_PREFIX: $_SKILL_PREFIX" source <(~/.claude/skills/gstack/bin/gstack-repo-mode 2>/dev/null) || true REPO_MODE=${REPO_MODE:-unknown} echo "REPO_MODE: $REPO_MODE" +# Auto-migrate legacy ~/.gstack/projects/ data to project-local .gstack/ (once per project) +eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" || true +if [ -n "${PROJECT_DATA_DIR:-}" ] && [ -d "$HOME/.gstack/projects/${SLUG:-}" ] && [ ! -f "${PROJECT_DATA_DIR}/.migrated" ]; then + ~/.claude/skills/gstack/bin/gstack-migrate-local 2>/dev/null && touch "${PROJECT_DATA_DIR}/.migrated" 2>/dev/null || true +fi _LAKE_SEEN=$([ -f ~/.gstack/.completeness-intro-seen ] && echo "yes" || echo "no") echo "LAKE_INTRO: $_LAKE_SEEN" _TEL=$(~/.claude/skills/gstack/bin/gstack-config get telemetry 2>/dev/null || true) diff --git a/investigate/SKILL.md b/investigate/SKILL.md index 8e307dc0b..a69ff0753 100644 --- a/investigate/SKILL.md +++ b/investigate/SKILL.md @@ -55,6 +55,11 @@ echo "SKILL_PREFIX: $_SKILL_PREFIX" source <(~/.claude/skills/gstack/bin/gstack-repo-mode 2>/dev/null) || true REPO_MODE=${REPO_MODE:-unknown} echo "REPO_MODE: $REPO_MODE" +# Auto-migrate legacy ~/.gstack/projects/ data to project-local .gstack/ (once per project) +eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" || true +if [ -n "${PROJECT_DATA_DIR:-}" ] && [ -d "$HOME/.gstack/projects/${SLUG:-}" ] && [ ! -f "${PROJECT_DATA_DIR}/.migrated" ]; then + ~/.claude/skills/gstack/bin/gstack-migrate-local 2>/dev/null && touch "${PROJECT_DATA_DIR}/.migrated" 2>/dev/null || true +fi _LAKE_SEEN=$([ -f ~/.gstack/.completeness-intro-seen ] && echo "yes" || echo "no") echo "LAKE_INTRO: $_LAKE_SEEN" _TEL=$(~/.claude/skills/gstack/bin/gstack-config get telemetry 2>/dev/null || true) diff --git a/land-and-deploy/SKILL.md b/land-and-deploy/SKILL.md index e54bb1594..09e7449da 100644 --- a/land-and-deploy/SKILL.md +++ b/land-and-deploy/SKILL.md @@ -490,11 +490,11 @@ and whether the deploy configuration has changed since then: ```bash eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" -if [ ! -f ~/.gstack/projects/$SLUG/land-deploy-confirmed ]; then +if [ ! -f "$PROJECT_DATA_DIR/land-deploy-confirmed" ] && [ ! -f ~/.gstack/projects/$SLUG/land-deploy-confirmed ]; then echo "FIRST_RUN" else # Check if deploy config has changed since confirmation - SAVED_HASH=$(cat ~/.gstack/projects/$SLUG/land-deploy-confirmed 2>/dev/null) + SAVED_HASH=$(cat "$PROJECT_DATA_DIR/land-deploy-confirmed" 2>/dev/null || cat ~/.gstack/projects/$SLUG/land-deploy-confirmed 2>/dev/null) CURRENT_HASH=$(sed -n '/## Deploy Configuration/,/^## /p' CLAUDE.md 2>/dev/null | shasum -a 256 | cut -d' ' -f1) # Also hash workflow files that affect deploy behavior WORKFLOW_HASH=$(find .github/workflows -maxdepth 1 \( -name '*deploy*' -o -name '*cd*' \) 2>/dev/null | xargs cat 2>/dev/null | shasum -a 256 | cut -d' ' -f1) @@ -1325,7 +1325,7 @@ Log to the review dashboard: ```bash eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" -mkdir -p ~/.gstack/projects/$SLUG +mkdir -p "$PROJECT_DATA_DIR" ``` Write a JSONL entry with timing data: diff --git a/land-and-deploy/SKILL.md.tmpl b/land-and-deploy/SKILL.md.tmpl index acec63c2e..4fa66f961 100644 --- a/land-and-deploy/SKILL.md.tmpl +++ b/land-and-deploy/SKILL.md.tmpl @@ -106,11 +106,11 @@ and whether the deploy configuration has changed since then: ```bash {{SLUG_EVAL}} -if [ ! -f ~/.gstack/projects/$SLUG/land-deploy-confirmed ]; then +if [ ! -f "$PROJECT_DATA_DIR/land-deploy-confirmed" ] && [ ! -f ~/.gstack/projects/$SLUG/land-deploy-confirmed ]; then echo "FIRST_RUN" else # Check if deploy config has changed since confirmation - SAVED_HASH=$(cat ~/.gstack/projects/$SLUG/land-deploy-confirmed 2>/dev/null) + SAVED_HASH=$(cat "$PROJECT_DATA_DIR/land-deploy-confirmed" 2>/dev/null || cat ~/.gstack/projects/$SLUG/land-deploy-confirmed 2>/dev/null) CURRENT_HASH=$(sed -n '/## Deploy Configuration/,/^## /p' CLAUDE.md 2>/dev/null | shasum -a 256 | cut -d' ' -f1) # Also hash workflow files that affect deploy behavior WORKFLOW_HASH=$(find .github/workflows -maxdepth 1 \( -name '*deploy*' -o -name '*cd*' \) 2>/dev/null | xargs cat 2>/dev/null | shasum -a 256 | cut -d' ' -f1) @@ -875,7 +875,7 @@ Log to the review dashboard: ```bash {{SLUG_EVAL}} -mkdir -p ~/.gstack/projects/$SLUG +mkdir -p "$PROJECT_DATA_DIR" ``` Write a JSONL entry with timing data: diff --git a/office-hours/SKILL.md b/office-hours/SKILL.md index 34aa90707..db4ccc33d 100644 --- a/office-hours/SKILL.md +++ b/office-hours/SKILL.md @@ -46,6 +46,11 @@ echo "SKILL_PREFIX: $_SKILL_PREFIX" source <(~/.claude/skills/gstack/bin/gstack-repo-mode 2>/dev/null) || true REPO_MODE=${REPO_MODE:-unknown} echo "REPO_MODE: $REPO_MODE" +# Auto-migrate legacy ~/.gstack/projects/ data to project-local .gstack/ (once per project) +eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" || true +if [ -n "${PROJECT_DATA_DIR:-}" ] && [ -d "$HOME/.gstack/projects/${SLUG:-}" ] && [ ! -f "${PROJECT_DATA_DIR}/.migrated" ]; then + ~/.claude/skills/gstack/bin/gstack-migrate-local 2>/dev/null && touch "${PROJECT_DATA_DIR}/.migrated" 2>/dev/null || true +fi _LAKE_SEEN=$([ -f ~/.gstack/.completeness-intro-seen ] && echo "yes" || echo "no") echo "LAKE_INTRO: $_LAKE_SEEN" _TEL=$(~/.claude/skills/gstack/bin/gstack-config get telemetry 2>/dev/null || true) @@ -396,7 +401,7 @@ eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" 4. **List existing design docs for this project:** ```bash setopt +o nomatch 2>/dev/null || true # zsh compat - ls -t ~/.gstack/projects/$SLUG/*-design-*.md 2>/dev/null + ls -t "$PROJECT_DATA_DIR"/*-design-*.md 2>/dev/null ``` If design docs exist, list them: "Prior designs for this project: [titles + dates]" @@ -627,14 +632,14 @@ After the user states the problem (first question in Phase 2A or 2B), search exi Extract 3-5 significant keywords from the user's problem statement and grep across design docs: ```bash setopt +o nomatch 2>/dev/null || true # zsh compat -grep -li "\|\|" ~/.gstack/projects/$SLUG/*-design-*.md 2>/dev/null +grep -li "\|\|" "$PROJECT_DATA_DIR"/*-design-*.md 2>/dev/null ``` If matches found, read the matching design docs and surface them: - "FYI: Related design found — '{title}' by {user} on {date} (branch: {branch}). Key overlap: {1-line summary of relevant section}." - Ask via AskUserQuestion: "Should we build on this prior design or start fresh?" -This enables cross-team discovery — multiple users exploring the same project will see each other's design docs in `~/.gstack/projects/`. +This enables cross-team discovery — multiple users exploring the same project will see each other's design docs in `.gstack/`. If no matches found, proceed silently. @@ -858,7 +863,7 @@ Generating visual mockups of the proposed design... (say "skip" if you don't nee ```bash eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" -_DESIGN_DIR=~/.gstack/projects/$SLUG/designs/mockup-$(date +%Y%m%d) +_DESIGN_DIR="$PROJECT_DATA_DIR"/designs/mockup-$(date +%Y%m%d) mkdir -p "$_DESIGN_DIR" echo "DESIGN_DIR: $_DESIGN_DIR" ``` @@ -1024,7 +1029,7 @@ Count the signals. You'll use this count in Phase 6 to determine which tier of c Write the design document to the project directory. ```bash -eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" && mkdir -p ~/.gstack/projects/$SLUG +eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" && mkdir -p "$PROJECT_DATA_DIR" USER=$(whoami) DATETIME=$(date +%Y%m%d-%H%M%S) ``` @@ -1032,11 +1037,11 @@ DATETIME=$(date +%Y%m%d-%H%M%S) **Design lineage:** Before writing, check for existing design docs on this branch: ```bash setopt +o nomatch 2>/dev/null || true # zsh compat -PRIOR=$(ls -t ~/.gstack/projects/$SLUG/*-$BRANCH-design-*.md 2>/dev/null | head -1) +PRIOR=$(ls -t "$PROJECT_DATA_DIR"/*-$BRANCH-design-*.md 2>/dev/null | head -1) ``` If `$PRIOR` exists, the new doc gets a `Supersedes:` field referencing it. This creates a revision chain — you can trace how a design evolved across office hours sessions. -Write to `~/.gstack/projects/{slug}/{user}-{branch}-design-{datetime}.md`: +Write to `.gstack/{user}-{branch}-design-{datetime}.md`: ### Startup mode design doc template: @@ -1301,7 +1306,7 @@ After the plea, suggest the next step: - **`/plan-eng-review`** for well-scoped implementation planning — lock in architecture, tests, edge cases - **`/plan-design-review`** for visual/UX design review -The design doc at `~/.gstack/projects/` is automatically discoverable by downstream skills — they will read it during their pre-review system audit. +The design doc at `.gstack/` is automatically discoverable by downstream skills — they will read it during their pre-review system audit. --- diff --git a/office-hours/SKILL.md.tmpl b/office-hours/SKILL.md.tmpl index 4b5a5e192..64353ffe0 100644 --- a/office-hours/SKILL.md.tmpl +++ b/office-hours/SKILL.md.tmpl @@ -49,7 +49,7 @@ Understand the project and the area the user wants to change. 4. **List existing design docs for this project:** ```bash setopt +o nomatch 2>/dev/null || true # zsh compat - ls -t ~/.gstack/projects/$SLUG/*-design-*.md 2>/dev/null + ls -t "$PROJECT_DATA_DIR"/*-design-*.md 2>/dev/null ``` If design docs exist, list them: "Prior designs for this project: [titles + dates]" @@ -280,14 +280,14 @@ After the user states the problem (first question in Phase 2A or 2B), search exi Extract 3-5 significant keywords from the user's problem statement and grep across design docs: ```bash setopt +o nomatch 2>/dev/null || true # zsh compat -grep -li "\|\|" ~/.gstack/projects/$SLUG/*-design-*.md 2>/dev/null +grep -li "\|\|" "$PROJECT_DATA_DIR"/*-design-*.md 2>/dev/null ``` If matches found, read the matching design docs and surface them: - "FYI: Related design found — '{title}' by {user} on {date} (branch: {branch}). Key overlap: {1-line summary of relevant section}." - Ask via AskUserQuestion: "Should we build on this prior design or start fresh?" -This enables cross-team discovery — multiple users exploring the same project will see each other's design docs in `~/.gstack/projects/`. +This enables cross-team discovery — multiple users exploring the same project will see each other's design docs in `.gstack/`. If no matches found, proceed silently. @@ -427,11 +427,11 @@ DATETIME=$(date +%Y%m%d-%H%M%S) **Design lineage:** Before writing, check for existing design docs on this branch: ```bash setopt +o nomatch 2>/dev/null || true # zsh compat -PRIOR=$(ls -t ~/.gstack/projects/$SLUG/*-$BRANCH-design-*.md 2>/dev/null | head -1) +PRIOR=$(ls -t "$PROJECT_DATA_DIR"/*-$BRANCH-design-*.md 2>/dev/null | head -1) ``` If `$PRIOR` exists, the new doc gets a `Supersedes:` field referencing it. This creates a revision chain — you can trace how a design evolved across office hours sessions. -Write to `~/.gstack/projects/{slug}/{user}-{branch}-design-{datetime}.md`: +Write to `.gstack/{user}-{branch}-design-{datetime}.md`: ### Startup mode design doc template: @@ -636,7 +636,7 @@ After the plea, suggest the next step: - **`/plan-eng-review`** for well-scoped implementation planning — lock in architecture, tests, edge cases - **`/plan-design-review`** for visual/UX design review -The design doc at `~/.gstack/projects/` is automatically discoverable by downstream skills — they will read it during their pre-review system audit. +The design doc at `.gstack/` is automatically discoverable by downstream skills — they will read it during their pre-review system audit. --- diff --git a/plan-ceo-review/SKILL.md b/plan-ceo-review/SKILL.md index f208894ce..46ff7551e 100644 --- a/plan-ceo-review/SKILL.md +++ b/plan-ceo-review/SKILL.md @@ -44,6 +44,11 @@ echo "SKILL_PREFIX: $_SKILL_PREFIX" source <(~/.claude/skills/gstack/bin/gstack-repo-mode 2>/dev/null) || true REPO_MODE=${REPO_MODE:-unknown} echo "REPO_MODE: $REPO_MODE" +# Auto-migrate legacy ~/.gstack/projects/ data to project-local .gstack/ (once per project) +eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" || true +if [ -n "${PROJECT_DATA_DIR:-}" ] && [ -d "$HOME/.gstack/projects/${SLUG:-}" ] && [ ! -f "${PROJECT_DATA_DIR}/.migrated" ]; then + ~/.claude/skills/gstack/bin/gstack-migrate-local 2>/dev/null && touch "${PROJECT_DATA_DIR}/.migrated" 2>/dev/null || true +fi _LAKE_SEEN=$([ -f ~/.gstack/.completeness-intro-seen ] && echo "yes" || echo "no") echo "LAKE_INTRO: $_LAKE_SEEN" _TEL=$(~/.claude/skills/gstack/bin/gstack-config get telemetry 2>/dev/null || true) @@ -468,10 +473,12 @@ Then read CLAUDE.md, TODOS.md, and any existing architecture docs. **Design doc check:** ```bash setopt +o nomatch 2>/dev/null || true # zsh compat -SLUG=$(~/.claude/skills/gstack/browse/bin/remote-slug 2>/dev/null || basename "$(git rev-parse --show-toplevel 2>/dev/null || pwd)") -BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null | tr '/' '-' || echo 'no-branch') -DESIGN=$(ls -t ~/.gstack/projects/$SLUG/*-$BRANCH-design-*.md 2>/dev/null | head -1) -[ -z "$DESIGN" ] && DESIGN=$(ls -t ~/.gstack/projects/$SLUG/*-design-*.md 2>/dev/null | head -1) +eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" +DESIGN=$(ls -t "$PROJECT_DATA_DIR"/designs/*-$BRANCH-design-*.md 2>/dev/null | head -1) +[ -z "$DESIGN" ] && DESIGN=$(ls -t "$PROJECT_DATA_DIR"/designs/*-design-*.md 2>/dev/null | head -1) +# Fallback: legacy global path +[ -z "$DESIGN" ] && DESIGN=$(ls -t "$PROJECT_DATA_DIR"/*-$BRANCH-design-*.md 2>/dev/null | head -1) +[ -z "$DESIGN" ] && DESIGN=$(ls -t "$PROJECT_DATA_DIR"/*-design-*.md 2>/dev/null | head -1) [ -n "$DESIGN" ] && echo "Design doc found: $DESIGN" || echo "No design doc found" ``` If a design doc exists (from `/office-hours`), read it. Use it as the source of truth for the problem statement, constraints, and chosen approach. If it has a `Supersedes:` field, note that this is a revised design. @@ -479,7 +486,7 @@ If a design doc exists (from `/office-hours`), read it. Use it as the source of **Handoff note check** (reuses $SLUG and $BRANCH from the design doc check above): ```bash setopt +o nomatch 2>/dev/null || true # zsh compat -HANDOFF=$(ls -t ~/.gstack/projects/$SLUG/*-$BRANCH-ceo-handoff-*.md 2>/dev/null | head -1) +HANDOFF=$(ls -t "$PROJECT_DATA_DIR"/*-$BRANCH-ceo-handoff-*.md 2>/dev/null | head -1) [ -n "$HANDOFF" ] && echo "HANDOFF_FOUND: $HANDOFF" || echo "NO_HANDOFF" ``` If this block runs in a separate shell from the design doc check, recompute $SLUG and $BRANCH first using the same commands from that block. @@ -534,10 +541,12 @@ If the Read fails (file not found), say: After /office-hours completes, re-run the design doc check: ```bash setopt +o nomatch 2>/dev/null || true # zsh compat -SLUG=$(~/.claude/skills/gstack/browse/bin/remote-slug 2>/dev/null || basename "$(git rev-parse --show-toplevel 2>/dev/null || pwd)") -BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null | tr '/' '-' || echo 'no-branch') -DESIGN=$(ls -t ~/.gstack/projects/$SLUG/*-$BRANCH-design-*.md 2>/dev/null | head -1) -[ -z "$DESIGN" ] && DESIGN=$(ls -t ~/.gstack/projects/$SLUG/*-design-*.md 2>/dev/null | head -1) +eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" +DESIGN=$(ls -t "$PROJECT_DATA_DIR"/designs/*-$BRANCH-design-*.md 2>/dev/null | head -1) +[ -z "$DESIGN" ] && DESIGN=$(ls -t "$PROJECT_DATA_DIR"/designs/*-design-*.md 2>/dev/null | head -1) +# Fallback: legacy global path +[ -z "$DESIGN" ] && DESIGN=$(ls -t "$PROJECT_DATA_DIR"/*-$BRANCH-design-*.md 2>/dev/null | head -1) +[ -z "$DESIGN" ] && DESIGN=$(ls -t "$PROJECT_DATA_DIR"/*-design-*.md 2>/dev/null | head -1) [ -n "$DESIGN" ] && echo "Design doc found: $DESIGN" || echo "No design doc found" ``` @@ -680,17 +689,17 @@ Rules: After the opt-in/cherry-pick ceremony, write the plan to disk so the vision and decisions survive beyond this conversation. Only run this step for EXPANSION and SELECTIVE EXPANSION modes. ```bash -eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" && mkdir -p ~/.gstack/projects/$SLUG/ceo-plans +eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" && mkdir -p "$PROJECT_DATA_DIR"/ceo-plans ``` Before writing, check for existing CEO plans in the ceo-plans/ directory. If any are >30 days old or their branch has been merged/deleted, offer to archive them: ```bash -mkdir -p ~/.gstack/projects/$SLUG/ceo-plans/archive -# For each stale plan: mv ~/.gstack/projects/$SLUG/ceo-plans/{old-plan}.md ~/.gstack/projects/$SLUG/ceo-plans/archive/ +mkdir -p "$PROJECT_DATA_DIR"/ceo-plans/archive +# For each stale plan: mv "$PROJECT_DATA_DIR"/ceo-plans/{old-plan}.md "$PROJECT_DATA_DIR"/ceo-plans/archive/ ``` -Write to `~/.gstack/projects/$SLUG/ceo-plans/{date}-{feature-slug}.md` using this format: +Write to `"$PROJECT_DATA_DIR"/ceo-plans/{date}-{feature-slug}.md` using this format: ```markdown --- @@ -1318,7 +1327,7 @@ the review is complete and the context is no longer needed. ```bash setopt +o nomatch 2>/dev/null || true # zsh compat eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" -rm -f ~/.gstack/projects/$SLUG/*-$BRANCH-ceo-handoff-*.md 2>/dev/null || true +rm -f "$PROJECT_DATA_DIR"/*-$BRANCH-ceo-handoff-*.md 2>/dev/null || true ``` ## Review Log @@ -1485,7 +1494,7 @@ At the end of the review, if the vision produced a compelling feature direction, "The vision from this review produced {N} accepted scope expansions. Want to promote it to a design doc in the repo?" - **A)** Promote to `docs/designs/{FEATURE}.md` (committed to repo, visible to the team) -- **B)** Keep in `~/.gstack/projects/` only (local, personal reference) +- **B)** Keep in `.gstack/` only (local, personal reference) - **C)** Skip If promoted, copy the CEO plan content to `docs/designs/{FEATURE}.md` (create the directory if needed) and update the `status` field in the original CEO plan from `ACTIVE` to `PROMOTED`. diff --git a/plan-ceo-review/SKILL.md.tmpl b/plan-ceo-review/SKILL.md.tmpl index 8f6aebe3b..e95228ac0 100644 --- a/plan-ceo-review/SKILL.md.tmpl +++ b/plan-ceo-review/SKILL.md.tmpl @@ -106,10 +106,12 @@ Then read CLAUDE.md, TODOS.md, and any existing architecture docs. **Design doc check:** ```bash setopt +o nomatch 2>/dev/null || true # zsh compat -SLUG=$(~/.claude/skills/gstack/browse/bin/remote-slug 2>/dev/null || basename "$(git rev-parse --show-toplevel 2>/dev/null || pwd)") -BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null | tr '/' '-' || echo 'no-branch') -DESIGN=$(ls -t ~/.gstack/projects/$SLUG/*-$BRANCH-design-*.md 2>/dev/null | head -1) -[ -z "$DESIGN" ] && DESIGN=$(ls -t ~/.gstack/projects/$SLUG/*-design-*.md 2>/dev/null | head -1) +eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" +DESIGN=$(ls -t "$PROJECT_DATA_DIR"/designs/*-$BRANCH-design-*.md 2>/dev/null | head -1) +[ -z "$DESIGN" ] && DESIGN=$(ls -t "$PROJECT_DATA_DIR"/designs/*-design-*.md 2>/dev/null | head -1) +# Fallback: legacy global path +[ -z "$DESIGN" ] && DESIGN=$(ls -t "$PROJECT_DATA_DIR"/*-$BRANCH-design-*.md 2>/dev/null | head -1) +[ -z "$DESIGN" ] && DESIGN=$(ls -t "$PROJECT_DATA_DIR"/*-design-*.md 2>/dev/null | head -1) [ -n "$DESIGN" ] && echo "Design doc found: $DESIGN" || echo "No design doc found" ``` If a design doc exists (from `/office-hours`), read it. Use it as the source of truth for the problem statement, constraints, and chosen approach. If it has a `Supersedes:` field, note that this is a revised design. @@ -117,7 +119,7 @@ If a design doc exists (from `/office-hours`), read it. Use it as the source of **Handoff note check** (reuses $SLUG and $BRANCH from the design doc check above): ```bash setopt +o nomatch 2>/dev/null || true # zsh compat -HANDOFF=$(ls -t ~/.gstack/projects/$SLUG/*-$BRANCH-ceo-handoff-*.md 2>/dev/null | head -1) +HANDOFF=$(ls -t "$PROJECT_DATA_DIR"/*-$BRANCH-ceo-handoff-*.md 2>/dev/null | head -1) [ -n "$HANDOFF" ] && echo "HANDOFF_FOUND: $HANDOFF" || echo "NO_HANDOFF" ``` If this block runs in a separate shell from the design doc check, recompute $SLUG and $BRANCH first using the same commands from that block. @@ -268,17 +270,17 @@ Rules: After the opt-in/cherry-pick ceremony, write the plan to disk so the vision and decisions survive beyond this conversation. Only run this step for EXPANSION and SELECTIVE EXPANSION modes. ```bash -eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" && mkdir -p ~/.gstack/projects/$SLUG/ceo-plans +eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" && mkdir -p "$PROJECT_DATA_DIR"/ceo-plans ``` Before writing, check for existing CEO plans in the ceo-plans/ directory. If any are >30 days old or their branch has been merged/deleted, offer to archive them: ```bash -mkdir -p ~/.gstack/projects/$SLUG/ceo-plans/archive -# For each stale plan: mv ~/.gstack/projects/$SLUG/ceo-plans/{old-plan}.md ~/.gstack/projects/$SLUG/ceo-plans/archive/ +mkdir -p "$PROJECT_DATA_DIR"/ceo-plans/archive +# For each stale plan: mv "$PROJECT_DATA_DIR"/ceo-plans/{old-plan}.md "$PROJECT_DATA_DIR"/ceo-plans/archive/ ``` -Write to `~/.gstack/projects/$SLUG/ceo-plans/{date}-{feature-slug}.md` using this format: +Write to `"$PROJECT_DATA_DIR"/ceo-plans/{date}-{feature-slug}.md` using this format: ```markdown --- @@ -715,7 +717,7 @@ the review is complete and the context is no longer needed. ```bash setopt +o nomatch 2>/dev/null || true # zsh compat {{SLUG_EVAL}} -rm -f ~/.gstack/projects/$SLUG/*-$BRANCH-ceo-handoff-*.md 2>/dev/null || true +rm -f "$PROJECT_DATA_DIR"/*-$BRANCH-ceo-handoff-*.md 2>/dev/null || true ``` ## Review Log @@ -768,7 +770,7 @@ At the end of the review, if the vision produced a compelling feature direction, "The vision from this review produced {N} accepted scope expansions. Want to promote it to a design doc in the repo?" - **A)** Promote to `docs/designs/{FEATURE}.md` (committed to repo, visible to the team) -- **B)** Keep in `~/.gstack/projects/` only (local, personal reference) +- **B)** Keep in `.gstack/` only (local, personal reference) - **C)** Skip If promoted, copy the CEO plan content to `docs/designs/{FEATURE}.md` (create the directory if needed) and update the `status` field in the original CEO plan from `ACTIVE` to `PROMOTED`. diff --git a/plan-design-review/SKILL.md b/plan-design-review/SKILL.md index 902055a0b..ba7618666 100644 --- a/plan-design-review/SKILL.md +++ b/plan-design-review/SKILL.md @@ -42,6 +42,11 @@ echo "SKILL_PREFIX: $_SKILL_PREFIX" source <(~/.claude/skills/gstack/bin/gstack-repo-mode 2>/dev/null) || true REPO_MODE=${REPO_MODE:-unknown} echo "REPO_MODE: $REPO_MODE" +# Auto-migrate legacy ~/.gstack/projects/ data to project-local .gstack/ (once per project) +eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" || true +if [ -n "${PROJECT_DATA_DIR:-}" ] && [ -d "$HOME/.gstack/projects/${SLUG:-}" ] && [ ! -f "${PROJECT_DATA_DIR}/.migrated" ]; then + ~/.claude/skills/gstack/bin/gstack-migrate-local 2>/dev/null && touch "${PROJECT_DATA_DIR}/.migrated" 2>/dev/null || true +fi _LAKE_SEEN=$([ -f ~/.gstack/.completeness-intro-seen ] && echo "yes" || echo "no") echo "LAKE_INTRO: $_LAKE_SEEN" _TEL=$(~/.claude/skills/gstack/bin/gstack-config get telemetry 2>/dev/null || true) diff --git a/plan-eng-review/SKILL.md b/plan-eng-review/SKILL.md index c00869315..96b8cc9e1 100644 --- a/plan-eng-review/SKILL.md +++ b/plan-eng-review/SKILL.md @@ -43,6 +43,11 @@ echo "SKILL_PREFIX: $_SKILL_PREFIX" source <(~/.claude/skills/gstack/bin/gstack-repo-mode 2>/dev/null) || true REPO_MODE=${REPO_MODE:-unknown} echo "REPO_MODE: $REPO_MODE" +# Auto-migrate legacy ~/.gstack/projects/ data to project-local .gstack/ (once per project) +eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" || true +if [ -n "${PROJECT_DATA_DIR:-}" ] && [ -d "$HOME/.gstack/projects/${SLUG:-}" ] && [ ! -f "${PROJECT_DATA_DIR}/.migrated" ]; then + ~/.claude/skills/gstack/bin/gstack-migrate-local 2>/dev/null && touch "${PROJECT_DATA_DIR}/.migrated" 2>/dev/null || true +fi _LAKE_SEEN=$([ -f ~/.gstack/.completeness-intro-seen ] && echo "yes" || echo "no") echo "LAKE_INTRO: $_LAKE_SEEN" _TEL=$(~/.claude/skills/gstack/bin/gstack-config get telemetry 2>/dev/null || true) @@ -394,10 +399,12 @@ When evaluating architecture, think "boring by default." When reviewing tests, t ### Design Doc Check ```bash setopt +o nomatch 2>/dev/null || true # zsh compat -SLUG=$(~/.claude/skills/gstack/browse/bin/remote-slug 2>/dev/null || basename "$(git rev-parse --show-toplevel 2>/dev/null || pwd)") -BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null | tr '/' '-' || echo 'no-branch') -DESIGN=$(ls -t ~/.gstack/projects/$SLUG/*-$BRANCH-design-*.md 2>/dev/null | head -1) -[ -z "$DESIGN" ] && DESIGN=$(ls -t ~/.gstack/projects/$SLUG/*-design-*.md 2>/dev/null | head -1) +eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" +DESIGN=$(ls -t "$PROJECT_DATA_DIR"/designs/*-$BRANCH-design-*.md 2>/dev/null | head -1) +[ -z "$DESIGN" ] && DESIGN=$(ls -t "$PROJECT_DATA_DIR"/designs/*-design-*.md 2>/dev/null | head -1) +# Fallback: legacy global path +[ -z "$DESIGN" ] && DESIGN=$(ls -t "$PROJECT_DATA_DIR"/*-$BRANCH-design-*.md 2>/dev/null | head -1) +[ -z "$DESIGN" ] && DESIGN=$(ls -t "$PROJECT_DATA_DIR"/*-design-*.md 2>/dev/null | head -1) [ -n "$DESIGN" ] && echo "Design doc found: $DESIGN" || echo "No design doc found" ``` If a design doc exists, read it. Use it as the source of truth for the problem statement, constraints, and chosen approach. If it has a `Supersedes:` field, note that this is a revised design — check the prior version for context on what changed and why. @@ -444,10 +451,12 @@ If the Read fails (file not found), say: After /office-hours completes, re-run the design doc check: ```bash setopt +o nomatch 2>/dev/null || true # zsh compat -SLUG=$(~/.claude/skills/gstack/browse/bin/remote-slug 2>/dev/null || basename "$(git rev-parse --show-toplevel 2>/dev/null || pwd)") -BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null | tr '/' '-' || echo 'no-branch') -DESIGN=$(ls -t ~/.gstack/projects/$SLUG/*-$BRANCH-design-*.md 2>/dev/null | head -1) -[ -z "$DESIGN" ] && DESIGN=$(ls -t ~/.gstack/projects/$SLUG/*-design-*.md 2>/dev/null | head -1) +eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" +DESIGN=$(ls -t "$PROJECT_DATA_DIR"/designs/*-$BRANCH-design-*.md 2>/dev/null | head -1) +[ -z "$DESIGN" ] && DESIGN=$(ls -t "$PROJECT_DATA_DIR"/designs/*-design-*.md 2>/dev/null | head -1) +# Fallback: legacy global path +[ -z "$DESIGN" ] && DESIGN=$(ls -t "$PROJECT_DATA_DIR"/*-$BRANCH-design-*.md 2>/dev/null | head -1) +[ -z "$DESIGN" ] && DESIGN=$(ls -t "$PROJECT_DATA_DIR"/*-design-*.md 2>/dev/null | head -1) [ -n "$DESIGN" ] && echo "Design doc found: $DESIGN" || echo "No design doc found" ``` @@ -681,12 +690,12 @@ The plan should be complete enough that when implementation begins, every test i After producing the coverage diagram, write a test plan artifact to the project directory so `/qa` and `/qa-only` can consume it as primary test input: ```bash -eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" && mkdir -p ~/.gstack/projects/$SLUG +eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" && mkdir -p "$PROJECT_DATA_DIR" USER=$(whoami) DATETIME=$(date +%Y%m%d-%H%M%S) ``` -Write to `~/.gstack/projects/{slug}/{user}-{branch}-eng-review-test-plan-{datetime}.md`: +Write to `.gstack/{user}-{branch}-eng-review-test-plan-{datetime}.md`: ```markdown # Test Plan diff --git a/plan-eng-review/SKILL.md.tmpl b/plan-eng-review/SKILL.md.tmpl index c91e96d78..da61aef43 100644 --- a/plan-eng-review/SKILL.md.tmpl +++ b/plan-eng-review/SKILL.md.tmpl @@ -71,8 +71,8 @@ When evaluating architecture, think "boring by default." When reviewing tests, t setopt +o nomatch 2>/dev/null || true # zsh compat SLUG=$(~/.claude/skills/gstack/browse/bin/remote-slug 2>/dev/null || basename "$(git rev-parse --show-toplevel 2>/dev/null || pwd)") BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null | tr '/' '-' || echo 'no-branch') -DESIGN=$(ls -t ~/.gstack/projects/$SLUG/*-$BRANCH-design-*.md 2>/dev/null | head -1) -[ -z "$DESIGN" ] && DESIGN=$(ls -t ~/.gstack/projects/$SLUG/*-design-*.md 2>/dev/null | head -1) +DESIGN=$(ls -t "$PROJECT_DATA_DIR/designs/"*-$BRANCH-design-*.md 2>/dev/null || ls -t ~/.gstack/projects/$SLUG/*-$BRANCH-design-*.md 2>/dev/null | head -1) +[ -z "$DESIGN" ] && DESIGN=$(ls -t "$PROJECT_DATA_DIR/designs/"*-design-*.md 2>/dev/null || ls -t ~/.gstack/projects/$SLUG/*-design-*.md 2>/dev/null | head -1) [ -n "$DESIGN" ] && echo "Design doc found: $DESIGN" || echo "No design doc found" ``` If a design doc exists, read it. Use it as the source of truth for the problem statement, constraints, and chosen approach. If it has a `Supersedes:` field, note that this is a revised design — check the prior version for context on what changed and why. diff --git a/qa-only/SKILL.md b/qa-only/SKILL.md index 6161dc313..9210733d6 100644 --- a/qa-only/SKILL.md +++ b/qa-only/SKILL.md @@ -39,6 +39,11 @@ echo "SKILL_PREFIX: $_SKILL_PREFIX" source <(~/.claude/skills/gstack/bin/gstack-repo-mode 2>/dev/null) || true REPO_MODE=${REPO_MODE:-unknown} echo "REPO_MODE: $REPO_MODE" +# Auto-migrate legacy ~/.gstack/projects/ data to project-local .gstack/ (once per project) +eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" || true +if [ -n "${PROJECT_DATA_DIR:-}" ] && [ -d "$HOME/.gstack/projects/${SLUG:-}" ] && [ ! -f "${PROJECT_DATA_DIR}/.migrated" ]; then + ~/.claude/skills/gstack/bin/gstack-migrate-local 2>/dev/null && touch "${PROJECT_DATA_DIR}/.migrated" 2>/dev/null || true +fi _LAKE_SEEN=$([ -f ~/.gstack/.completeness-intro-seen ] && echo "yes" || echo "no") echo "LAKE_INTRO: $_LAKE_SEEN" _TEL=$(~/.claude/skills/gstack/bin/gstack-config get telemetry 2>/dev/null || true) @@ -400,11 +405,11 @@ mkdir -p "$REPORT_DIR/screenshots" Before falling back to git diff heuristics, check for richer test plan sources: -1. **Project-scoped test plans:** Check `~/.gstack/projects/` for recent `*-test-plan-*.md` files for this repo +1. **Project-scoped test plans:** Check `.gstack/plans/` for recent `*-test-plan-*.md` files for this repo ```bash setopt +o nomatch 2>/dev/null || true # zsh compat eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" - ls -t ~/.gstack/projects/$SLUG/*-test-plan-*.md 2>/dev/null | head -1 + ls -t "$PROJECT_DATA_DIR/plans/"*-test-plan-*.md 2>/dev/null || ls -t ~/.gstack/projects/$SLUG/*-test-plan-*.md 2>/dev/null | head -1 ``` 2. **Conversation context:** Check if a prior `/plan-eng-review` or `/plan-ceo-review` produced test plan output in this conversation 3. **Use whichever source is richer.** Fall back to git diff analysis only if neither is available. @@ -699,9 +704,9 @@ Write the report to both local and project-scoped locations: **Project-scoped:** Write test outcome artifact for cross-session context: ```bash -eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" && mkdir -p ~/.gstack/projects/$SLUG +eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" && mkdir -p "$PROJECT_DATA_DIR" ``` -Write to `~/.gstack/projects/{slug}/{user}-{branch}-test-outcome-{datetime}.md` +Write to `.gstack/{user}-{branch}-test-outcome-{datetime}.md` ### Output Structure diff --git a/qa-only/SKILL.md.tmpl b/qa-only/SKILL.md.tmpl index 0bb59c0c0..f8826c674 100644 --- a/qa-only/SKILL.md.tmpl +++ b/qa-only/SKILL.md.tmpl @@ -53,11 +53,11 @@ mkdir -p "$REPORT_DIR/screenshots" Before falling back to git diff heuristics, check for richer test plan sources: -1. **Project-scoped test plans:** Check `~/.gstack/projects/` for recent `*-test-plan-*.md` files for this repo +1. **Project-scoped test plans:** Check `.gstack/plans/` for recent `*-test-plan-*.md` files for this repo ```bash setopt +o nomatch 2>/dev/null || true # zsh compat {{SLUG_EVAL}} - ls -t ~/.gstack/projects/$SLUG/*-test-plan-*.md 2>/dev/null | head -1 + ls -t "$PROJECT_DATA_DIR/plans/"*-test-plan-*.md 2>/dev/null || ls -t ~/.gstack/projects/$SLUG/*-test-plan-*.md 2>/dev/null | head -1 ``` 2. **Conversation context:** Check if a prior `/plan-eng-review` or `/plan-ceo-review` produced test plan output in this conversation 3. **Use whichever source is richer.** Fall back to git diff analysis only if neither is available. @@ -78,7 +78,7 @@ Write the report to both local and project-scoped locations: ```bash {{SLUG_SETUP}} ``` -Write to `~/.gstack/projects/{slug}/{user}-{branch}-test-outcome-{datetime}.md` +Write to `.gstack/{user}-{branch}-test-outcome-{datetime}.md` ### Output Structure diff --git a/qa/SKILL.md b/qa/SKILL.md index bf532784a..b9499e24d 100644 --- a/qa/SKILL.md +++ b/qa/SKILL.md @@ -45,6 +45,11 @@ echo "SKILL_PREFIX: $_SKILL_PREFIX" source <(~/.claude/skills/gstack/bin/gstack-repo-mode 2>/dev/null) || true REPO_MODE=${REPO_MODE:-unknown} echo "REPO_MODE: $REPO_MODE" +# Auto-migrate legacy ~/.gstack/projects/ data to project-local .gstack/ (once per project) +eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" || true +if [ -n "${PROJECT_DATA_DIR:-}" ] && [ -d "$HOME/.gstack/projects/${SLUG:-}" ] && [ ! -f "${PROJECT_DATA_DIR}/.migrated" ]; then + ~/.claude/skills/gstack/bin/gstack-migrate-local 2>/dev/null && touch "${PROJECT_DATA_DIR}/.migrated" 2>/dev/null || true +fi _LAKE_SEEN=$([ -f ~/.gstack/.completeness-intro-seen ] && echo "yes" || echo "no") echo "LAKE_INTRO: $_LAKE_SEEN" _TEL=$(~/.claude/skills/gstack/bin/gstack-config get telemetry 2>/dev/null || true) @@ -630,11 +635,11 @@ mkdir -p .gstack/qa-reports/screenshots Before falling back to git diff heuristics, check for richer test plan sources: -1. **Project-scoped test plans:** Check `~/.gstack/projects/` for recent `*-test-plan-*.md` files for this repo +1. **Project-scoped test plans:** Check `.gstack/plans/` for recent `*-test-plan-*.md` files for this repo ```bash setopt +o nomatch 2>/dev/null || true # zsh compat eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" - ls -t ~/.gstack/projects/$SLUG/*-test-plan-*.md 2>/dev/null | head -1 + ls -t "$PROJECT_DATA_DIR/plans/"*-test-plan-*.md 2>/dev/null || ls -t ~/.gstack/projects/$SLUG/*-test-plan-*.md 2>/dev/null | head -1 ``` 2. **Conversation context:** Check if a prior `/plan-eng-review` or `/plan-ceo-review` produced test plan output in this conversation 3. **Use whichever source is richer.** Fall back to git diff analysis only if neither is available. @@ -1097,9 +1102,9 @@ Write the report to both local and project-scoped locations: **Project-scoped:** Write test outcome artifact for cross-session context: ```bash -eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" && mkdir -p ~/.gstack/projects/$SLUG +eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" && mkdir -p "$PROJECT_DATA_DIR" ``` -Write to `~/.gstack/projects/{slug}/{user}-{branch}-test-outcome-{datetime}.md` +Write to `.gstack/{user}-{branch}-test-outcome-{datetime}.md` **Per-issue additions** (beyond standard report template): - Fix Status: verified / best-effort / reverted / deferred diff --git a/qa/SKILL.md.tmpl b/qa/SKILL.md.tmpl index 0283ffc7c..6e657c4c0 100644 --- a/qa/SKILL.md.tmpl +++ b/qa/SKILL.md.tmpl @@ -94,11 +94,11 @@ mkdir -p .gstack/qa-reports/screenshots Before falling back to git diff heuristics, check for richer test plan sources: -1. **Project-scoped test plans:** Check `~/.gstack/projects/` for recent `*-test-plan-*.md` files for this repo +1. **Project-scoped test plans:** Check `.gstack/plans/` for recent `*-test-plan-*.md` files for this repo ```bash setopt +o nomatch 2>/dev/null || true # zsh compat {{SLUG_EVAL}} - ls -t ~/.gstack/projects/$SLUG/*-test-plan-*.md 2>/dev/null | head -1 + ls -t "$PROJECT_DATA_DIR/plans/"*-test-plan-*.md 2>/dev/null || ls -t ~/.gstack/projects/$SLUG/*-test-plan-*.md 2>/dev/null | head -1 ``` 2. **Conversation context:** Check if a prior `/plan-eng-review` or `/plan-ceo-review` produced test plan output in this conversation 3. **Use whichever source is richer.** Fall back to git diff analysis only if neither is available. @@ -287,7 +287,7 @@ Write the report to both local and project-scoped locations: ```bash {{SLUG_SETUP}} ``` -Write to `~/.gstack/projects/{slug}/{user}-{branch}-test-outcome-{datetime}.md` +Write to `.gstack/{user}-{branch}-test-outcome-{datetime}.md` **Per-issue additions** (beyond standard report template): - Fix Status: verified / best-effort / reverted / deferred diff --git a/retro/SKILL.md b/retro/SKILL.md index 3ebc40fec..49d81ab93 100644 --- a/retro/SKILL.md +++ b/retro/SKILL.md @@ -39,6 +39,11 @@ echo "SKILL_PREFIX: $_SKILL_PREFIX" source <(~/.claude/skills/gstack/bin/gstack-repo-mode 2>/dev/null) || true REPO_MODE=${REPO_MODE:-unknown} echo "REPO_MODE: $REPO_MODE" +# Auto-migrate legacy ~/.gstack/projects/ data to project-local .gstack/ (once per project) +eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" || true +if [ -n "${PROJECT_DATA_DIR:-}" ] && [ -d "$HOME/.gstack/projects/${SLUG:-}" ] && [ ! -f "${PROJECT_DATA_DIR}/.migrated" ]; then + ~/.claude/skills/gstack/bin/gstack-migrate-local 2>/dev/null && touch "${PROJECT_DATA_DIR}/.migrated" 2>/dev/null || true +fi _LAKE_SEEN=$([ -f ~/.gstack/.completeness-intro-seen ] && echo "yes" || echo "no") echo "LAKE_INTRO: $_LAKE_SEEN" _TEL=$(~/.claude/skills/gstack/bin/gstack-config get telemetry 2>/dev/null || true) @@ -804,7 +809,7 @@ Check review JSONL logs for plan completion data from /ship runs this period: ```bash setopt +o nomatch 2>/dev/null || true # zsh compat eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" -cat ~/.gstack/projects/$SLUG/*-reviews.jsonl 2>/dev/null | grep '"skill":"ship"' | grep '"plan_items_total"' || echo "NO_PLAN_DATA" +cat "$PROJECT_DATA_DIR"/*-reviews.jsonl 2>/dev/null || cat ~/.gstack/projects/$SLUG/*-reviews.jsonl 2>/dev/null | grep '"skill":"ship"' | grep '"plan_items_total"' || echo "NO_PLAN_DATA" ``` If plan completion data exists within the retro time window: diff --git a/retro/SKILL.md.tmpl b/retro/SKILL.md.tmpl index 5463d07a9..56469ad6e 100644 --- a/retro/SKILL.md.tmpl +++ b/retro/SKILL.md.tmpl @@ -460,7 +460,7 @@ Check review JSONL logs for plan completion data from /ship runs this period: ```bash setopt +o nomatch 2>/dev/null || true # zsh compat eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" -cat ~/.gstack/projects/$SLUG/*-reviews.jsonl 2>/dev/null | grep '"skill":"ship"' | grep '"plan_items_total"' || echo "NO_PLAN_DATA" +cat "$PROJECT_DATA_DIR"/*-reviews.jsonl 2>/dev/null || cat ~/.gstack/projects/$SLUG/*-reviews.jsonl 2>/dev/null | grep '"skill":"ship"' | grep '"plan_items_total"' || echo "NO_PLAN_DATA" ``` If plan completion data exists within the retro time window: diff --git a/review/SKILL.md b/review/SKILL.md index 9b47b6902..f6fd32840 100644 --- a/review/SKILL.md +++ b/review/SKILL.md @@ -42,6 +42,11 @@ echo "SKILL_PREFIX: $_SKILL_PREFIX" source <(~/.claude/skills/gstack/bin/gstack-repo-mode 2>/dev/null) || true REPO_MODE=${REPO_MODE:-unknown} echo "REPO_MODE: $REPO_MODE" +# Auto-migrate legacy ~/.gstack/projects/ data to project-local .gstack/ (once per project) +eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" || true +if [ -n "${PROJECT_DATA_DIR:-}" ] && [ -d "$HOME/.gstack/projects/${SLUG:-}" ] && [ ! -f "${PROJECT_DATA_DIR}/.migrated" ]; then + ~/.claude/skills/gstack/bin/gstack-migrate-local 2>/dev/null && touch "${PROJECT_DATA_DIR}/.migrated" 2>/dev/null || true +fi _LAKE_SEEN=$([ -f ~/.gstack/.completeness-intro-seen ] && echo "yes" || echo "no") echo "LAKE_INTRO: $_LAKE_SEEN" _TEL=$(~/.claude/skills/gstack/bin/gstack-config get telemetry 2>/dev/null || true) diff --git a/review/greptile-triage.md b/review/greptile-triage.md index 3cb6e8d59..9402c9cf9 100644 --- a/review/greptile-triage.md +++ b/review/greptile-triage.md @@ -34,8 +34,8 @@ The `position != null` filter on line-level comments automatically skips outdate Derive the project-specific history path: ```bash -REMOTE_SLUG=$(browse/bin/remote-slug 2>/dev/null || ~/.claude/skills/gstack/browse/bin/remote-slug 2>/dev/null || basename "$(git rev-parse --show-toplevel 2>/dev/null || pwd)") -PROJECT_HISTORY="$HOME/.gstack/projects/$REMOTE_SLUG/greptile-history.md" +eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" +PROJECT_HISTORY="$PROJECT_DATA_DIR/greptile-history.md" ``` Read `$PROJECT_HISTORY` if it exists (per-project suppressions). Each line records a previous triage outcome: @@ -183,13 +183,13 @@ When classifying comments, also assess whether Greptile's implied severity match Before writing, ensure both directories exist: ```bash -REMOTE_SLUG=$(browse/bin/remote-slug 2>/dev/null || ~/.claude/skills/gstack/browse/bin/remote-slug 2>/dev/null || basename "$(git rev-parse --show-toplevel 2>/dev/null || pwd)") -mkdir -p "$HOME/.gstack/projects/$REMOTE_SLUG" +eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" +mkdir -p "$PROJECT_DATA_DIR" mkdir -p ~/.gstack ``` Append one line per triage outcome to **both** files (per-project for suppressions, global for retro): -- `~/.gstack/projects/$REMOTE_SLUG/greptile-history.md` (per-project) +- `$PROJECT_DATA_DIR/greptile-history.md` (per-project) - `~/.gstack/greptile-history.md` (global aggregate) Format: diff --git a/scripts/resolvers/design.ts b/scripts/resolvers/design.ts index 6f97e7921..ed968cbb1 100644 --- a/scripts/resolvers/design.ts +++ b/scripts/resolvers/design.ts @@ -307,9 +307,9 @@ Compare screenshots and observations across pages for: **Project-scoped:** \`\`\`bash -eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" && mkdir -p ~/.gstack/projects/$SLUG +eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" && mkdir -p "$PROJECT_DATA_DIR/designs" \`\`\` -Write to: \`~/.gstack/projects/{slug}/{user}-{branch}-design-audit-{datetime}.md\` +Write to: \`.gstack/designs/{user}-{branch}-design-audit-{datetime}.md\` **Baseline:** Write \`design-baseline.json\` for regression mode: \`\`\`json diff --git a/scripts/resolvers/preamble.ts b/scripts/resolvers/preamble.ts index f70574520..eca833bc0 100644 --- a/scripts/resolvers/preamble.ts +++ b/scripts/resolvers/preamble.ts @@ -43,6 +43,11 @@ echo "SKILL_PREFIX: $_SKILL_PREFIX" source <(${ctx.paths.binDir}/gstack-repo-mode 2>/dev/null) || true REPO_MODE=\${REPO_MODE:-unknown} echo "REPO_MODE: $REPO_MODE" +# Auto-migrate legacy ~/.gstack/projects/ data to project-local .gstack/ (once per project) +eval "$(${ctx.paths.binDir}/gstack-slug 2>/dev/null)" || true +if [ -n "\${PROJECT_DATA_DIR:-}" ] && [ -d "$HOME/.gstack/projects/\${SLUG:-}" ] && [ ! -f "\${PROJECT_DATA_DIR}/.migrated" ]; then + ${ctx.paths.binDir}/gstack-migrate-local 2>/dev/null && touch "\${PROJECT_DATA_DIR}/.migrated" 2>/dev/null || true +fi _LAKE_SEEN=$([ -f ~/.gstack/.completeness-intro-seen ] && echo "yes" || echo "no") echo "LAKE_INTRO: $_LAKE_SEEN" _TEL=$(${ctx.paths.binDir}/gstack-config get telemetry 2>/dev/null || true) diff --git a/scripts/resolvers/review.ts b/scripts/resolvers/review.ts index 02fd77659..41347b3b6 100644 --- a/scripts/resolvers/review.ts +++ b/scripts/resolvers/review.ts @@ -250,9 +250,11 @@ If the Read fails (file not found), say: After /${first} completes, re-run the design doc check: \`\`\`bash setopt +o nomatch 2>/dev/null || true # zsh compat -SLUG=$(~/.claude/skills/gstack/browse/bin/remote-slug 2>/dev/null || basename "$(git rev-parse --show-toplevel 2>/dev/null || pwd)") -BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null | tr '/' '-' || echo 'no-branch') -DESIGN=$(ls -t ~/.gstack/projects/$SLUG/*-$BRANCH-design-*.md 2>/dev/null | head -1) +eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" +DESIGN=$(ls -t "$PROJECT_DATA_DIR"/designs/*-$BRANCH-design-*.md 2>/dev/null | head -1) +[ -z "$DESIGN" ] && DESIGN=$(ls -t "$PROJECT_DATA_DIR"/designs/*-design-*.md 2>/dev/null | head -1) +# Fallback: legacy global path +[ -z "$DESIGN" ] && DESIGN=$(ls -t ~/.gstack/projects/$SLUG/*-$BRANCH-design-*.md 2>/dev/null | head -1) [ -z "$DESIGN" ] && DESIGN=$(ls -t ~/.gstack/projects/$SLUG/*-design-*.md 2>/dev/null | head -1) [ -n "$DESIGN" ] && echo "Design doc found: $DESIGN" || echo "No design doc found" \`\`\` diff --git a/scripts/resolvers/testing.ts b/scripts/resolvers/testing.ts index da1381c20..31adeefa4 100644 --- a/scripts/resolvers/testing.ts +++ b/scripts/resolvers/testing.ts @@ -401,12 +401,12 @@ The plan should be complete enough that when implementation begins, every test i After producing the coverage diagram, write a test plan artifact to the project directory so \`/qa\` and \`/qa-only\` can consume it as primary test input: \`\`\`bash -eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" && mkdir -p ~/.gstack/projects/$SLUG +eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" && mkdir -p "$PROJECT_DATA_DIR/plans" USER=$(whoami) DATETIME=$(date +%Y%m%d-%H%M%S) \`\`\` -Write to \`~/.gstack/projects/{slug}/{user}-{branch}-eng-review-test-plan-{datetime}.md\`: +Write to \`.gstack/plans/{user}-{branch}-eng-review-test-plan-{datetime}.md\`: \`\`\`markdown # Test Plan @@ -498,12 +498,12 @@ Using the coverage percentage from the diagram in substep 4 (the \`COVERAGE: X/Y After producing the coverage diagram, write a test plan artifact so \`/qa\` and \`/qa-only\` can consume it: \`\`\`bash -eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" && mkdir -p ~/.gstack/projects/$SLUG +eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" && mkdir -p "$PROJECT_DATA_DIR/plans" USER=$(whoami) DATETIME=$(date +%Y%m%d-%H%M%S) \`\`\` -Write to \`~/.gstack/projects/{slug}/{user}-{branch}-ship-test-plan-{datetime}.md\`: +Write to \`.gstack/plans/{user}-{branch}-ship-test-plan-{datetime}.md\`: \`\`\`markdown # Test Plan diff --git a/scripts/resolvers/utility.ts b/scripts/resolvers/utility.ts index 48e9c0d82..f23e317c3 100644 --- a/scripts/resolvers/utility.ts +++ b/scripts/resolvers/utility.ts @@ -5,7 +5,7 @@ export function generateSlugEval(ctx: TemplateContext): string { } export function generateSlugSetup(ctx: TemplateContext): string { - return `eval "$(${ctx.paths.binDir}/gstack-slug 2>/dev/null)" && mkdir -p ~/.gstack/projects/$SLUG`; + return `eval "$(${ctx.paths.binDir}/gstack-slug 2>/dev/null)" && mkdir -p "$PROJECT_DATA_DIR"`; } export function generateBaseBranchDetect(_ctx: TemplateContext): string { diff --git a/setup-browser-cookies/SKILL.md b/setup-browser-cookies/SKILL.md index 69617692f..7f53fe97d 100644 --- a/setup-browser-cookies/SKILL.md +++ b/setup-browser-cookies/SKILL.md @@ -36,6 +36,11 @@ echo "SKILL_PREFIX: $_SKILL_PREFIX" source <(~/.claude/skills/gstack/bin/gstack-repo-mode 2>/dev/null) || true REPO_MODE=${REPO_MODE:-unknown} echo "REPO_MODE: $REPO_MODE" +# Auto-migrate legacy ~/.gstack/projects/ data to project-local .gstack/ (once per project) +eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" || true +if [ -n "${PROJECT_DATA_DIR:-}" ] && [ -d "$HOME/.gstack/projects/${SLUG:-}" ] && [ ! -f "${PROJECT_DATA_DIR}/.migrated" ]; then + ~/.claude/skills/gstack/bin/gstack-migrate-local 2>/dev/null && touch "${PROJECT_DATA_DIR}/.migrated" 2>/dev/null || true +fi _LAKE_SEEN=$([ -f ~/.gstack/.completeness-intro-seen ] && echo "yes" || echo "no") echo "LAKE_INTRO: $_LAKE_SEEN" _TEL=$(~/.claude/skills/gstack/bin/gstack-config get telemetry 2>/dev/null || true) diff --git a/setup-deploy/SKILL.md b/setup-deploy/SKILL.md index a0ff129c2..5f2623a7b 100644 --- a/setup-deploy/SKILL.md +++ b/setup-deploy/SKILL.md @@ -42,6 +42,11 @@ echo "SKILL_PREFIX: $_SKILL_PREFIX" source <(~/.claude/skills/gstack/bin/gstack-repo-mode 2>/dev/null) || true REPO_MODE=${REPO_MODE:-unknown} echo "REPO_MODE: $REPO_MODE" +# Auto-migrate legacy ~/.gstack/projects/ data to project-local .gstack/ (once per project) +eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" || true +if [ -n "${PROJECT_DATA_DIR:-}" ] && [ -d "$HOME/.gstack/projects/${SLUG:-}" ] && [ ! -f "${PROJECT_DATA_DIR}/.migrated" ]; then + ~/.claude/skills/gstack/bin/gstack-migrate-local 2>/dev/null && touch "${PROJECT_DATA_DIR}/.migrated" 2>/dev/null || true +fi _LAKE_SEEN=$([ -f ~/.gstack/.completeness-intro-seen ] && echo "yes" || echo "no") echo "LAKE_INTRO: $_LAKE_SEEN" _TEL=$(~/.claude/skills/gstack/bin/gstack-config get telemetry 2>/dev/null || true) diff --git a/ship/SKILL.md b/ship/SKILL.md index de2743f83..27319b131 100644 --- a/ship/SKILL.md +++ b/ship/SKILL.md @@ -40,6 +40,11 @@ echo "SKILL_PREFIX: $_SKILL_PREFIX" source <(~/.claude/skills/gstack/bin/gstack-repo-mode 2>/dev/null) || true REPO_MODE=${REPO_MODE:-unknown} echo "REPO_MODE: $REPO_MODE" +# Auto-migrate legacy ~/.gstack/projects/ data to project-local .gstack/ (once per project) +eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" || true +if [ -n "${PROJECT_DATA_DIR:-}" ] && [ -d "$HOME/.gstack/projects/${SLUG:-}" ] && [ ! -f "${PROJECT_DATA_DIR}/.migrated" ]; then + ~/.claude/skills/gstack/bin/gstack-migrate-local 2>/dev/null && touch "${PROJECT_DATA_DIR}/.migrated" 2>/dev/null || true +fi _LAKE_SEEN=$([ -f ~/.gstack/.completeness-intro-seen ] && echo "yes" || echo "no") echo "LAKE_INTRO: $_LAKE_SEEN" _TEL=$(~/.claude/skills/gstack/bin/gstack-config get telemetry 2>/dev/null || true) @@ -1111,12 +1116,12 @@ Using the coverage percentage from the diagram in substep 4 (the `COVERAGE: X/Y After producing the coverage diagram, write a test plan artifact so `/qa` and `/qa-only` can consume it: ```bash -eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" && mkdir -p ~/.gstack/projects/$SLUG +eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" && mkdir -p "$PROJECT_DATA_DIR/plans" USER=$(whoami) DATETIME=$(date +%Y%m%d-%H%M%S) ``` -Write to `~/.gstack/projects/{slug}/{user}-{branch}-ship-test-plan-{datetime}.md`: +Write to `.gstack/plans/{user}-{branch}-ship-test-plan-{datetime}.md`: ```markdown # Test Plan @@ -1410,13 +1415,6 @@ Present Codex output under a `CODEX (design):` header, merged with the checklist If no issues found: `Pre-Landing Review: No issues found.` -9. Persist the review result to the review log: -```bash -~/.claude/skills/gstack/bin/gstack-review-log '{"skill":"review","timestamp":"TIMESTAMP","status":"STATUS","issues_found":N,"critical":N,"informational":N,"commit":"'"$(git rev-parse --short HEAD)"'","via":"ship"}' -``` -Substitute TIMESTAMP (ISO 8601), STATUS ("clean" if no issues, "issues_found" otherwise), -and N values from the summary counts above. The `via:"ship"` distinguishes from standalone `/review` runs. - Save the review output — it goes into the PR body in Step 8. --- @@ -1622,26 +1620,10 @@ High-confidence findings (agreed on by multiple sources) should be prioritized f 1. Read `CHANGELOG.md` header to know the format. -2. **First, enumerate every commit on the branch:** - ```bash - git log ..HEAD --oneline - ``` - Copy the full list. Count the commits. You will use this as a checklist. - -3. **Read the full diff** to understand what each commit actually changed: - ```bash - git diff ...HEAD - ``` - -4. **Group commits by theme** before writing anything. Common themes: - - New features / capabilities - - Performance improvements - - Bug fixes - - Dead code removal / cleanup - - Infrastructure / tooling / tests - - Refactoring - -5. **Write the CHANGELOG entry** covering ALL groups: +2. Auto-generate the entry from **ALL commits on the branch** (not just recent ones): + - Use `git log ..HEAD --oneline` to see every commit being shipped + - Use `git diff ...HEAD` to see the full diff against the base branch + - The CHANGELOG entry must be comprehensive of ALL changes going into the PR - If existing CHANGELOG entries on the branch already cover some commits, replace them with one unified entry for the new version - Categorize changes into applicable sections: - `### Added` — new features @@ -1652,11 +1634,6 @@ High-confidence findings (agreed on by multiple sources) should be prioritized f - Insert after the file header (line 5), dated today - Format: `## [X.Y.Z.W] - YYYY-MM-DD` -6. **Cross-check:** Compare your CHANGELOG entry against the commit list from step 2. - Every commit must map to at least one bullet point. If any commit is unrepresented, - add it now. If the branch has N commits spanning K themes, the CHANGELOG must - reflect all K themes. - **Do NOT ask the user to describe changes.** Infer from the diff and commit history. --- @@ -1794,12 +1771,7 @@ The PR/MR body should contain these sections: ``` ## Summary -..HEAD --oneline` to enumerate -every commit. Exclude the VERSION/CHANGELOG metadata commit (that's this PR's bookkeeping, -not a substantive change). Group the remaining commits into logical sections (e.g., -"**Performance**", "**Dead Code Removal**", "**Infrastructure**"). Every substantive commit -must appear in at least one section. If a commit's work isn't reflected in the summary, -you missed it.> + ## Test Coverage @@ -1894,13 +1866,13 @@ doc updates — the user runs `/ship` and documentation stays current without a Log coverage and plan completion data so `/retro` can track trends: ```bash -eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" && mkdir -p ~/.gstack/projects/$SLUG +eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" && mkdir -p "$PROJECT_DATA_DIR" ``` -Append to `~/.gstack/projects/$SLUG/$BRANCH-reviews.jsonl`: +Append to `$PROJECT_DATA_DIR/$BRANCH-reviews.jsonl`: ```bash -echo '{"skill":"ship","timestamp":"'"$(date -u +%Y-%m-%dT%H:%M:%SZ)"'","coverage_pct":COVERAGE_PCT,"plan_items_total":PLAN_TOTAL,"plan_items_done":PLAN_DONE,"verification_result":"VERIFY_RESULT","version":"VERSION","branch":"BRANCH"}' >> ~/.gstack/projects/$SLUG/$BRANCH-reviews.jsonl +echo '{"skill":"ship","timestamp":"'"$(date -u +%Y-%m-%dT%H:%M:%SZ)"'","coverage_pct":COVERAGE_PCT,"plan_items_total":PLAN_TOTAL,"plan_items_done":PLAN_DONE,"verification_result":"VERIFY_RESULT","version":"VERSION","branch":"BRANCH"}' >> "$PROJECT_DATA_DIR/$BRANCH-reviews.jsonl" ``` Substitute from earlier steps: diff --git a/ship/SKILL.md.tmpl b/ship/SKILL.md.tmpl index 62842fc52..89f899a60 100644 --- a/ship/SKILL.md.tmpl +++ b/ship/SKILL.md.tmpl @@ -264,13 +264,6 @@ Review the diff for structural issues that tests don't catch. If no issues found: `Pre-Landing Review: No issues found.` -9. Persist the review result to the review log: -```bash -~/.claude/skills/gstack/bin/gstack-review-log '{"skill":"review","timestamp":"TIMESTAMP","status":"STATUS","issues_found":N,"critical":N,"informational":N,"commit":"'"$(git rev-parse --short HEAD)"'","via":"ship"}' -``` -Substitute TIMESTAMP (ISO 8601), STATUS ("clean" if no issues, "issues_found" otherwise), -and N values from the summary counts above. The `via:"ship"` distinguishes from standalone `/review` runs. - Save the review output — it goes into the PR body in Step 8. --- @@ -339,26 +332,10 @@ For each classified comment: 1. Read `CHANGELOG.md` header to know the format. -2. **First, enumerate every commit on the branch:** - ```bash - git log ..HEAD --oneline - ``` - Copy the full list. Count the commits. You will use this as a checklist. - -3. **Read the full diff** to understand what each commit actually changed: - ```bash - git diff ...HEAD - ``` - -4. **Group commits by theme** before writing anything. Common themes: - - New features / capabilities - - Performance improvements - - Bug fixes - - Dead code removal / cleanup - - Infrastructure / tooling / tests - - Refactoring - -5. **Write the CHANGELOG entry** covering ALL groups: +2. Auto-generate the entry from **ALL commits on the branch** (not just recent ones): + - Use `git log ..HEAD --oneline` to see every commit being shipped + - Use `git diff ...HEAD` to see the full diff against the base branch + - The CHANGELOG entry must be comprehensive of ALL changes going into the PR - If existing CHANGELOG entries on the branch already cover some commits, replace them with one unified entry for the new version - Categorize changes into applicable sections: - `### Added` — new features @@ -369,11 +346,6 @@ For each classified comment: - Insert after the file header (line 5), dated today - Format: `## [X.Y.Z.W] - YYYY-MM-DD` -6. **Cross-check:** Compare your CHANGELOG entry against the commit list from step 2. - Every commit must map to at least one bullet point. If any commit is unrepresented, - add it now. If the branch has N commits spanning K themes, the CHANGELOG must - reflect all K themes. - **Do NOT ask the user to describe changes.** Infer from the diff and commit history. --- @@ -511,12 +483,7 @@ The PR/MR body should contain these sections: ``` ## Summary -..HEAD --oneline` to enumerate -every commit. Exclude the VERSION/CHANGELOG metadata commit (that's this PR's bookkeeping, -not a substantive change). Group the remaining commits into logical sections (e.g., -"**Performance**", "**Dead Code Removal**", "**Infrastructure**"). Every substantive commit -must appear in at least one section. If a commit's work isn't reflected in the summary, -you missed it.> + ## Test Coverage @@ -611,13 +578,13 @@ doc updates — the user runs `/ship` and documentation stays current without a Log coverage and plan completion data so `/retro` can track trends: ```bash -eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" && mkdir -p ~/.gstack/projects/$SLUG +eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" && mkdir -p "$PROJECT_DATA_DIR" ``` -Append to `~/.gstack/projects/$SLUG/$BRANCH-reviews.jsonl`: +Append to `$PROJECT_DATA_DIR/$BRANCH-reviews.jsonl`: ```bash -echo '{"skill":"ship","timestamp":"'"$(date -u +%Y-%m-%dT%H:%M:%SZ)"'","coverage_pct":COVERAGE_PCT,"plan_items_total":PLAN_TOTAL,"plan_items_done":PLAN_DONE,"verification_result":"VERIFY_RESULT","version":"VERSION","branch":"BRANCH"}' >> ~/.gstack/projects/$SLUG/$BRANCH-reviews.jsonl +echo '{"skill":"ship","timestamp":"'"$(date -u +%Y-%m-%dT%H:%M:%SZ)"'","coverage_pct":COVERAGE_PCT,"plan_items_total":PLAN_TOTAL,"plan_items_done":PLAN_DONE,"verification_result":"VERIFY_RESULT","version":"VERSION","branch":"BRANCH"}' >> "$PROJECT_DATA_DIR/$BRANCH-reviews.jsonl" ``` Substitute from earlier steps: diff --git a/test/helpers/eval-store.ts b/test/helpers/eval-store.ts index a7d63178c..fa206b95e 100644 --- a/test/helpers/eval-store.ts +++ b/test/helpers/eval-store.ts @@ -22,7 +22,19 @@ const LEGACY_EVAL_DIR = path.join(os.homedir(), '.gstack-dev', 'evals'); */ export function getProjectEvalDir(): string { try { - // Try repo-local gstack-slug first, then global install + // Prefer project-local .gstack/evals/ via git root + const gitRoot = spawnSync('git', ['rev-parse', '--show-toplevel'], { + stdio: 'pipe', timeout: 3000, + }); + const root = gitRoot.stdout?.toString().trim(); + if (root) { + const dir = path.join(root, '.gstack', 'evals'); + fs.mkdirSync(dir, { recursive: true }); + return dir; + } + } catch { /* fall through */ } + try { + // Fallback: legacy slug-based path (backward-compatible read) const localSlug = spawnSync('bash', ['-c', '.claude/skills/gstack/bin/gstack-slug 2>/dev/null || ~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null'], { stdio: 'pipe', timeout: 3000, }); @@ -30,9 +42,8 @@ export function getProjectEvalDir(): string { if (output) { const slugMatch = output.match(/^SLUG=(.+)$/m); if (slugMatch && slugMatch[1]) { - const dir = path.join(os.homedir(), '.gstack', 'projects', slugMatch[1], 'evals'); - fs.mkdirSync(dir, { recursive: true }); - return dir; + const legacyDir = path.join(os.homedir(), '.gstack', 'projects', slugMatch[1], 'evals'); + if (fs.existsSync(legacyDir)) return legacyDir; } } } catch { /* fall through */ } diff --git a/test/skill-validation.test.ts b/test/skill-validation.test.ts index 7bb163d84..e386a55b1 100644 --- a/test/skill-validation.test.ts +++ b/test/skill-validation.test.ts @@ -282,26 +282,12 @@ describe('Update check preamble', () => { // --- Part 7: Cross-skill path consistency (A1) --- describe('Cross-skill path consistency', () => { - test('REMOTE_SLUG derivation pattern is identical across files that use it', () => { - const patterns = extractRemoteSlugPatterns(ROOT, ['qa', 'review']); - const allPatterns: string[] = []; - - for (const [, filePatterns] of patterns) { - allPatterns.push(...filePatterns); - } - - // Should find at least 2 occurrences (qa/SKILL.md + review/greptile-triage.md) - expect(allPatterns.length).toBeGreaterThanOrEqual(2); - - // All occurrences must be character-for-character identical - const unique = new Set(allPatterns); - if (unique.size > 1) { - const variants = Array.from(unique); - throw new Error( - `REMOTE_SLUG pattern differs across files:\n` + - variants.map((v, i) => ` ${i + 1}: ${v}`).join('\n') - ); - } + test('project data paths use $PROJECT_DATA_DIR from gstack-slug', () => { + // greptile-triage.md should use gstack-slug to set PROJECT_DATA_DIR + const triagePath = path.join(ROOT, 'review', 'greptile-triage.md'); + const content = fs.readFileSync(triagePath, 'utf-8'); + expect(content).toContain('$PROJECT_DATA_DIR/greptile-history.md'); + expect(content).toContain('gstack-slug'); }); test('all greptile-history write references specify both per-project and global paths', () => { @@ -317,15 +303,15 @@ describe('Cross-skill path consistency', () => { const content = fs.readFileSync(filePath, 'utf-8'); const hasBoth = (content.includes('per-project') && content.includes('global')) || - (content.includes('$REMOTE_SLUG/greptile-history') && content.includes('~/.gstack/greptile-history')); + (content.includes('$PROJECT_DATA_DIR/greptile-history') && content.includes('~/.gstack/greptile-history')); expect(hasBoth).toBe(true); } }); - test('greptile-triage.md contains both project and global history paths', () => { + test('greptile-triage.md contains both project-local and global history paths', () => { const content = fs.readFileSync(path.join(ROOT, 'review', 'greptile-triage.md'), 'utf-8'); - expect(content).toContain('$REMOTE_SLUG/greptile-history.md'); + expect(content).toContain('$PROJECT_DATA_DIR/greptile-history.md'); expect(content).toContain('~/.gstack/greptile-history.md'); }); @@ -984,9 +970,10 @@ describe('gstack-slug', () => { test('output is eval-compatible (KEY=VALUE format)', () => { const result = Bun.spawnSync([SLUG_BIN], { cwd: ROOT, stdout: 'pipe', stderr: 'pipe' }); const lines = result.stdout.toString().trim().split('\n'); - expect(lines.length).toBe(2); + expect(lines.length).toBe(3); expect(lines[0]).toMatch(/^SLUG=.+/); expect(lines[1]).toMatch(/^BRANCH=.+/); + expect(lines[2]).toMatch(/^PROJECT_DATA_DIR=.+/); }); test('output values contain only safe characters (no shell metacharacters)', () => {