diff --git a/TODOS.md b/TODOS.md index 9c114e04..0db295e5 100644 --- a/TODOS.md +++ b/TODOS.md @@ -427,24 +427,11 @@ Implemented as `supabase/functions/weekly-digest/index.ts`. pg_cron Monday 9am U ## Infrastructure -### /setup-gstack-upload skill (S3 bucket) +### ~~setup-gstack-upload~~ + ~~gstack-upload helper~~ — SUPERSEDED -**What:** Configure S3 bucket for image hosting. One-time setup for visual PR annotations. - -**Why:** Prerequisite for visual PR annotations in /ship and /review. - -**Effort:** M -**Priority:** P2 - -### gstack-upload helper - -**What:** `browse/bin/gstack-upload` — upload file to S3, return public URL. - -**Why:** Shared utility for all skills that need to embed images in PRs. - -**Effort:** S -**Priority:** P2 -**Depends on:** /setup-gstack-upload +Replaced by Supabase Storage (migration 008) + `bin/gstack-upload` + `lib/upload.ts`. +Screenshots upload to the team's Supabase Storage bucket with public CDN URLs. +No S3 needed. ### WebM to GIF conversion diff --git a/design-consultation/SKILL.md b/design-consultation/SKILL.md index 8fd9cb4b..b6950905 100644 --- a/design-consultation/SKILL.md +++ b/design-consultation/SKILL.md @@ -154,7 +154,7 @@ Look for brainstorm output: ```bash eval $(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null) -ls ~/.gstack/projects/$SLUG/*brainstorm* 2>/dev/null | head -5 +ls -t $PROJECTS_DIR/$SLUG/brainstorm/* 2>/dev/null | head -5 ls .context/*brainstorm* .context/attachments/*brainstorm* 2>/dev/null | head -5 ``` diff --git a/design-review/SKILL.md b/design-review/SKILL.md index b06e0827..a0a18c82 100644 --- a/design-review/SKILL.md +++ b/design-review/SKILL.md @@ -592,9 +592,10 @@ 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 +DATE=$(date +%Y-%m-%d) +mkdir -p $PROJECTS_DIR/$SLUG/reports ``` -Write to: `~/.gstack/projects/{slug}/{user}-{branch}-design-audit-{datetime}.md` +Write to: `$PROJECTS_DIR/$SLUG/reports/design-{domain}-$DATE.md` **Baseline:** Write `design-baseline.json` for regression mode: ```json @@ -809,11 +810,40 @@ Write the report to both local and project-scoped locations: **Local:** `.gstack/design-reports/design-audit-{domain}-{YYYY-MM-DD}.md` **Project-scoped:** + ```bash eval $(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null) -mkdir -p ~/.gstack/projects/$SLUG +DATE=$(date +%Y-%m-%d) ``` -Write to `~/.gstack/projects/{slug}/{user}-{branch}-design-audit-{datetime}.md` + +```bash +mkdir -p $PROJECTS_DIR/$SLUG/reports +FILE="$PROJECTS_DIR/$SLUG/reports/design-{domain}-$DATE.md" +[ -f "$FILE" ] && FILE="$PROJECTS_DIR/$SLUG/reports/design-{domain}-$DATE-$(date +%H%M).md" +``` + +Write to the file path resolved above. Include YAML frontmatter: +```yaml +--- +type: design-audit +branch: {branch} +date: {YYYY-MM-DD} +skill: design-review +--- +``` + +After writing, register in manifest: +```bash +~/.claude/skills/gstack/bin/gstack-manifest-append design-audit "reports/$(basename "$FILE")" design-review "$BRANCH" +``` + +**Screenshot upload:** After compiling the report, upload all screenshots: +```bash +for img in .gstack/design-reports/screenshots/*.png; do + [ -f "$img" ] && ~/.claude/skills/gstack/bin/gstack-upload "$img" 2>/dev/null +done +``` +If upload succeeds, update the report to use hosted URLs. If it fails, keep local paths and append: `(screenshot not uploaded — run gstack sync to share)` **Per-finding additions** (beyond standard design audit report): - Fix Status: verified / best-effort / reverted / deferred diff --git a/plan-ceo-review/SKILL.md b/plan-ceo-review/SKILL.md index ce799fe1..32a1a025 100644 --- a/plan-ceo-review/SKILL.md +++ b/plan-ceo-review/SKILL.md @@ -285,20 +285,28 @@ After the opt-in/cherry-pick ceremony, write the plan to disk so the vision and ```bash eval $(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null) -mkdir -p ~/.gstack/projects/$SLUG/ceo-plans +DATE=$(date +%Y-%m-%d) ``` -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 $PROJECTS_DIR/$SLUG/plans/ceo ``` -Write to `~/.gstack/projects/$SLUG/ceo-plans/{date}-{feature-slug}.md` using this format: +Before writing, check for existing CEO plans in the plans/ceo/ directory. If any are >30 days old or their branch has been merged/deleted, offer to archive them: + +```bash +mkdir -p $PROJECTS_DIR/$SLUG/plans/ceo/archive +# For each stale plan: mv $PROJECTS_DIR/$SLUG/plans/ceo/{old-plan}.md $PROJECTS_DIR/$SLUG/plans/ceo/archive/ +``` + +Write to `$PROJECTS_DIR/$SLUG/plans/ceo/$DATE-{feature-slug}.md` using this format: ```markdown --- +type: ceo-plan +branch: {branch} +date: {YYYY-MM-DD} +skill: plan-ceo-review status: ACTIVE --- # CEO Plan: {Feature Name} @@ -329,6 +337,11 @@ Repo: {owner/repo} Derive the feature slug from the plan being reviewed (e.g., "user-dashboard", "auth-refactor"). Use the date in YYYY-MM-DD format. +After writing the CEO plan, register it in the manifest: +```bash +~/.claude/skills/gstack/bin/gstack-manifest-append ceo-plan "plans/ceo/$DATE-{feature-slug}.md" plan-ceo-review "$BRANCH" +``` + ### 0E. Temporal Interrogation (EXPANSION, SELECTIVE EXPANSION, and HOLD modes) Think ahead to implementation: What decisions will need to be made during implementation that should be resolved NOW in the plan? ``` @@ -713,8 +726,8 @@ After producing the Completion Summary above, persist the review result: ```bash eval $(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null) -mkdir -p ~/.gstack/projects/$SLUG -echo '{"skill":"plan-ceo-review","timestamp":"TIMESTAMP","status":"STATUS","unresolved":N,"critical_gaps":N,"mode":"MODE"}' >> ~/.gstack/projects/$SLUG/$BRANCH-reviews.jsonl +mkdir -p $PROJECTS_DIR/$SLUG/reviews +echo '{"skill":"plan-ceo-review","timestamp":"TIMESTAMP","status":"STATUS","unresolved":N,"critical_gaps":N,"mode":"MODE"}' >> $PROJECTS_DIR/$SLUG/reviews/$BRANCH.jsonl ``` Before running this command, substitute the placeholder values from the Completion Summary you just produced: @@ -730,7 +743,7 @@ After completing the review, read the review log and config to display the dashb ```bash eval $(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null) -cat ~/.gstack/projects/$SLUG/$BRANCH-reviews.jsonl 2>/dev/null || echo "NO_REVIEWS" +cat $PROJECTS_DIR/$SLUG/reviews/$BRANCH.jsonl 2>/dev/null || echo "NO_REVIEWS" echo "---CONFIG---" ~/.claude/skills/gstack/bin/gstack-config get skip_eng_review 2>/dev/null || echo "false" ``` @@ -768,7 +781,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 `$PROJECTS_DIR/$SLUG/plans/ceo/` 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 507952c4..98495737 100644 --- a/plan-design-review/SKILL.md +++ b/plan-design-review/SKILL.md @@ -388,8 +388,8 @@ After producing the Completion Summary above, persist the review result: ```bash eval $(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null) -mkdir -p ~/.gstack/projects/$SLUG -echo '{"skill":"plan-design-review","timestamp":"TIMESTAMP","status":"STATUS","overall_score":N,"unresolved":N,"decisions_made":N}' >> ~/.gstack/projects/$SLUG/$BRANCH-reviews.jsonl +mkdir -p $PROJECTS_DIR/$SLUG/reviews +echo '{"skill":"plan-design-review","timestamp":"TIMESTAMP","status":"STATUS","overall_score":N,"unresolved":N,"decisions_made":N}' >> $PROJECTS_DIR/$SLUG/reviews/$BRANCH.jsonl ``` Substitute values from the Completion Summary: @@ -405,7 +405,7 @@ After completing the review, read the review log and config to display the dashb ```bash eval $(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null) -cat ~/.gstack/projects/$SLUG/$BRANCH-reviews.jsonl 2>/dev/null || echo "NO_REVIEWS" +cat $PROJECTS_DIR/$SLUG/reviews/$BRANCH.jsonl 2>/dev/null || echo "NO_REVIEWS" echo "---CONFIG---" ~/.claude/skills/gstack/bin/gstack-config get skip_eng_review 2>/dev/null || echo "false" ``` diff --git a/plan-eng-review/SKILL.md b/plan-eng-review/SKILL.md index 48fe7230..6f8e017a 100644 --- a/plan-eng-review/SKILL.md +++ b/plan-eng-review/SKILL.md @@ -217,14 +217,24 @@ After producing the test diagram, write a test plan artifact to the project dire ```bash eval $(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null) -USER=$(whoami) -DATETIME=$(date +%Y%m%d-%H%M%S) -mkdir -p ~/.gstack/projects/$SLUG +DATE=$(date +%Y-%m-%d) ``` -Write to `~/.gstack/projects/{slug}/{user}-{branch}-test-plan-{datetime}.md`: +```bash +mkdir -p $PROJECTS_DIR/$SLUG/plans +FILE="$PROJECTS_DIR/$SLUG/plans/$DATE-$BRANCH-test-plan.md" +[ -f "$FILE" ] && FILE="$PROJECTS_DIR/$SLUG/plans/$DATE-$(date +%H%M)-$BRANCH-test-plan.md" +``` + +Write to the file path resolved above with this content (include the YAML frontmatter): ```markdown +--- +type: test-plan +branch: {branch} +date: {YYYY-MM-DD} +skill: plan-eng-review +--- # Test Plan Generated by /plan-eng-review on {date} Branch: {branch} @@ -245,6 +255,11 @@ Repo: {owner/repo} This file is consumed by `/qa` and `/qa-only` as primary test input. Include only the information that helps a QA tester know **what to test and where** — not implementation details. +After writing the test plan, register it in the manifest: +```bash +~/.claude/skills/gstack/bin/gstack-manifest-append test-plan "plans/$(basename "$FILE")" plan-eng-review "$BRANCH" +``` + ### 4. Performance review Evaluate: * N+1 queries and database access patterns. @@ -326,8 +341,8 @@ After producing the Completion Summary above, persist the review result: ```bash eval $(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null) -mkdir -p ~/.gstack/projects/$SLUG -echo '{"skill":"plan-eng-review","timestamp":"TIMESTAMP","status":"STATUS","unresolved":N,"critical_gaps":N,"mode":"MODE"}' >> ~/.gstack/projects/$SLUG/$BRANCH-reviews.jsonl +mkdir -p $PROJECTS_DIR/$SLUG/reviews +echo '{"skill":"plan-eng-review","timestamp":"TIMESTAMP","status":"STATUS","unresolved":N,"critical_gaps":N,"mode":"MODE"}' >> $PROJECTS_DIR/$SLUG/reviews/$BRANCH.jsonl ``` Substitute values from the Completion Summary: @@ -343,7 +358,7 @@ After completing the review, read the review log and config to display the dashb ```bash eval $(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null) -cat ~/.gstack/projects/$SLUG/$BRANCH-reviews.jsonl 2>/dev/null || echo "NO_REVIEWS" +cat $PROJECTS_DIR/$SLUG/reviews/$BRANCH.jsonl 2>/dev/null || echo "NO_REVIEWS" echo "---CONFIG---" ~/.claude/skills/gstack/bin/gstack-config get skip_eng_review 2>/dev/null || echo "false" ``` diff --git a/qa-only/SKILL.md b/qa-only/SKILL.md index 594979b9..c00b9816 100644 --- a/qa-only/SKILL.md +++ b/qa-only/SKILL.md @@ -171,10 +171,10 @@ 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 the project plans directory for recent test plans ```bash 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 $PROJECTS_DIR/$SLUG/plans/*-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. @@ -465,11 +465,40 @@ Write the report to both local and project-scoped locations: **Local:** `.gstack/qa-reports/qa-report-{domain}-{YYYY-MM-DD}.md` **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 +DATE=$(date +%Y-%m-%d) ``` -Write to `~/.gstack/projects/{slug}/{user}-{branch}-test-outcome-{datetime}.md` + +```bash +mkdir -p $PROJECTS_DIR/$SLUG/reports +FILE="$PROJECTS_DIR/$SLUG/reports/$BRANCH-test-outcome-$DATE.md" +[ -f "$FILE" ] && FILE="$PROJECTS_DIR/$SLUG/reports/$BRANCH-test-outcome-$DATE-$(date +%H%M).md" +``` + +Write to the file path resolved above. Include YAML frontmatter: +```yaml +--- +type: test-outcome +branch: {branch} +date: {YYYY-MM-DD} +skill: qa-only +--- +``` + +After writing, register in manifest: +```bash +~/.claude/skills/gstack/bin/gstack-manifest-append test-outcome "reports/$(basename "$FILE")" qa-only "$BRANCH" +``` + +**Screenshot upload:** After compiling the report, upload all screenshots: +```bash +for img in .gstack/qa-reports/screenshots/*.png; do + [ -f "$img" ] && ~/.claude/skills/gstack/bin/gstack-upload "$img" 2>/dev/null +done +``` +If upload succeeds, update the report to use hosted URLs. If it fails, keep local paths and append: `(screenshot not uploaded — run gstack sync to share)` ### Output Structure diff --git a/qa/SKILL.md b/qa/SKILL.md index bad78095..498ab1c8 100644 --- a/qa/SKILL.md +++ b/qa/SKILL.md @@ -364,10 +364,10 @@ 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 the project plans directory for recent test plans ```bash 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 $PROJECTS_DIR/$SLUG/plans/*-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. @@ -1055,11 +1055,40 @@ Write the report to both local and project-scoped locations: **Local:** `.gstack/qa-reports/qa-report-{domain}-{YYYY-MM-DD}.md` **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 +DATE=$(date +%Y-%m-%d) ``` -Write to `~/.gstack/projects/{slug}/{user}-{branch}-test-outcome-{datetime}.md` + +```bash +mkdir -p $PROJECTS_DIR/$SLUG/reports +FILE="$PROJECTS_DIR/$SLUG/reports/$BRANCH-test-outcome-$DATE.md" +[ -f "$FILE" ] && FILE="$PROJECTS_DIR/$SLUG/reports/$BRANCH-test-outcome-$DATE-$(date +%H%M).md" +``` + +Write to the file path resolved above. Include YAML frontmatter: +```yaml +--- +type: test-outcome +branch: {branch} +date: {YYYY-MM-DD} +skill: qa +--- +``` + +After writing, register in manifest: +```bash +~/.claude/skills/gstack/bin/gstack-manifest-append test-outcome "reports/$(basename "$FILE")" qa "$BRANCH" +``` + +**Screenshot upload:** After compiling the report, upload all screenshots for team sharing: +```bash +for img in .gstack/qa-reports/screenshots/*.png; do + [ -f "$img" ] && ~/.claude/skills/gstack/bin/gstack-upload "$img" 2>/dev/null +done +``` +If upload succeeds, the output is a public URL. If it fails (no Supabase config), the local path is printed with a stderr warning. Either way, reference the screenshot path in the report. If URLs were returned, update the report to use hosted URLs instead of local paths. If local paths remain, append: `(screenshot not uploaded — run gstack sync to share)` **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 96ec9b8d..4ef68c87 100644 --- a/retro/SKILL.md +++ b/retro/SKILL.md @@ -384,7 +384,8 @@ Count backward from today — how many consecutive days have at least one commit Before saving the new snapshot, check for prior retro history: ```bash -ls -t .context/retros/*.json 2>/dev/null +eval $(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null) +ls -t $PROJECTS_DIR/$SLUG/retros/*.json 2>/dev/null ``` **If prior retros exist:** Load the most recent one using the Read tool. Calculate deltas for key metrics and include a **Trends vs Last Retro** section: @@ -405,16 +406,17 @@ Deep sessions: 3 → 5 ↑2 After computing all metrics (including streak) and loading any prior history for comparison, save a JSON snapshot: ```bash -mkdir -p .context/retros +eval $(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null) +mkdir -p $PROJECTS_DIR/$SLUG/retros ``` Determine the next sequence number for today (substitute the actual date for `$(date +%Y-%m-%d)`): ```bash # Count existing retros for today to get next sequence number today=$(TZ=America/Los_Angeles date +%Y-%m-%d) -existing=$(ls .context/retros/${today}-*.json 2>/dev/null | wc -l | tr -d ' ') +existing=$(ls $PROJECTS_DIR/$SLUG/retros/${today}-*.json 2>/dev/null | wc -l | tr -d ' ') next=$((existing + 1)) -# Save as .context/retros/${today}-${next}.json +# Save as $PROJECTS_DIR/$SLUG/retros/${today}-${next}.json ``` Use the Write tool to save the JSON file with this schema: @@ -480,9 +482,10 @@ Include backlog data in the JSON when TODOS.md exists: } ``` -After writing the JSON snapshot, sync to the team store (non-fatal, silent if not configured): +After writing the JSON snapshot, register in manifest and sync: ```bash -~/.claude/skills/gstack/bin/gstack-sync push-retro ".context/retros/${today}-${next}.json" 2>/dev/null && echo "Synced to team ✓" || true +~/.claude/skills/gstack/bin/gstack-manifest-append retro "retros/${today}-${next}.json" retro "$BRANCH" +~/.claude/skills/gstack/bin/gstack-sync push-retro "$PROJECTS_DIR/$SLUG/retros/${today}-${next}.json" 2>/dev/null && echo "Synced to team ✓" || true ~/.claude/skills/gstack/bin/gstack-sync push-transcript 2>/dev/null || true ``` @@ -595,7 +598,7 @@ When the user runs `/retro compare` (or `/retro compare 14d`): 2. Compute metrics for the immediately prior same-length window using both `--since` and `--until` to avoid overlap (e.g., `--since="14 days ago" --until="7 days ago"` for a 7d window) 3. Show a side-by-side comparison table with deltas and arrows 4. Write a brief narrative highlighting the biggest improvements and regressions -5. Save only the current-window snapshot to `.context/retros/` (same as a normal retro run); do **not** persist the prior-window metrics. +5. Save only the current-window snapshot to `$PROJECTS_DIR/$SLUG/retros/` (same as a normal retro run); do **not** persist the prior-window metrics. ## Tone @@ -608,11 +611,11 @@ When the user runs `/retro compare` (or `/retro compare 14d`): - Never compare teammates against each other negatively. Each person's section stands on its own. - Keep total output around 3000-4500 words (slightly longer to accommodate team sections) - Use markdown tables and code blocks for data, prose for narrative -- Output directly to the conversation — do NOT write to filesystem (except the `.context/retros/` JSON snapshot) +- Output directly to the conversation — do NOT write to filesystem (except the `$PROJECTS_DIR/$SLUG/retros/` JSON snapshot) ## Important Rules -- ALL narrative output goes directly to the user in the conversation. The ONLY file written is the `.context/retros/` JSON snapshot. +- ALL narrative output goes directly to the user in the conversation. The ONLY file written is the `$PROJECTS_DIR/$SLUG/retros/` JSON snapshot. - Use `origin/` for all git queries (not local main which may be stale) - Convert all timestamps to Pacific time for display (use `TZ=America/Los_Angeles`) - If the window has zero commits, say so and suggest a different window diff --git a/review/SKILL.md b/review/SKILL.md index 3a14a9d3..ed4c9a62 100644 --- a/review/SKILL.md +++ b/review/SKILL.md @@ -227,8 +227,8 @@ eval $(~/.claude/skills/gstack/bin/gstack-diff-scope 2>/dev/null) ```bash eval $(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null) -mkdir -p ~/.gstack/projects/$SLUG -echo '{"skill":"design-review-lite","timestamp":"TIMESTAMP","status":"STATUS","findings":N,"auto_fixed":M}' >> ~/.gstack/projects/$SLUG/$BRANCH-reviews.jsonl +mkdir -p $PROJECTS_DIR/$SLUG/reviews +echo '{"skill":"design-review-lite","timestamp":"TIMESTAMP","status":"STATUS","findings":N,"auto_fixed":M}' >> $PROJECTS_DIR/$SLUG/reviews/$BRANCH.jsonl ``` Substitute: TIMESTAMP = ISO 8601 datetime, STATUS = "clean" if 0 findings or "issues_found", N = total findings, M = auto-fixed count. diff --git a/review/greptile-triage.md b/review/greptile-triage.md index f8028dff..1c6c822a 100644 --- a/review/greptile-triage.md +++ b/review/greptile-triage.md @@ -212,7 +212,7 @@ After appending to both history files, sync each triage entry to the team store. cat > /tmp/gstack-greptile-entry.json << 'GEOF' { "date": "", - "repo": "", + "repo": "", "triage_type": "", "file_pattern": "", "category": "" diff --git a/setup-team-sync/SKILL.md b/setup-team-sync/SKILL.md index e3c33f4d..da6e54e3 100644 --- a/setup-team-sync/SKILL.md +++ b/setup-team-sync/SKILL.md @@ -25,22 +25,63 @@ touch ~/.gstack/sessions/"$PPID" _SESSIONS=$(find ~/.gstack/sessions -mmin -120 -type f 2>/dev/null | wc -l | tr -d ' ') find ~/.gstack/sessions -mmin +120 -type f -delete 2>/dev/null || true _CONTRIB=$(~/.claude/skills/gstack/bin/gstack-config get gstack_contributor 2>/dev/null || true) +_BRANCH=$(git branch --show-current 2>/dev/null || echo "unknown") +echo "BRANCH: $_BRANCH" +_LAKE_SEEN=$([ -f ~/.gstack/.completeness-intro-seen ] && echo "yes" || echo "no") +echo "LAKE_INTRO: $_LAKE_SEEN" ``` If output shows `UPGRADE_AVAILABLE `: read `~/.claude/skills/gstack/gstack-upgrade/SKILL.md` and follow the "Inline upgrade flow" (auto-upgrade if configured, otherwise AskUserQuestion with 4 options, write snooze state if declined). If `JUST_UPGRADED `: tell user "Running gstack v{to} (just updated!)" and continue. +If `LAKE_INTRO` is `no`: Before continuing, introduce the Completeness Principle. +Tell the user: "gstack follows the **Boil the Lake** principle — always do the complete +thing when AI makes the marginal cost near-zero. Read more: https://garryslist.org/posts/boil-the-ocean" +Then offer to open the essay in their default browser: + +```bash +open https://garryslist.org/posts/boil-the-ocean +touch ~/.gstack/.completeness-intro-seen +``` + +Only run `open` if the user says yes. Always run `touch` to mark as seen. This only happens once. + ## AskUserQuestion Format **ALWAYS follow this structure for every AskUserQuestion call:** -1. Context: project name, current branch, what we're working on (1-2 sentences) -2. The specific question or decision point -3. `RECOMMENDATION: Choose [X] because [one-line reason]` -4. Lettered options: `A) ... B) ... C) ...` +1. **Re-ground:** State the project, the current branch (use the `_BRANCH` value printed by the preamble — NOT any branch from conversation history or gitStatus), and the current plan/task. (1-2 sentences) +2. **Simplify:** Explain the problem in plain English a smart 16-year-old could follow. No raw function names, no internal jargon, no implementation details. Use concrete examples and analogies. Say what it DOES, not what it's called. +3. **Recommend:** `RECOMMENDATION: Choose [X] because [one-line reason]` — always prefer the complete option over shortcuts (see Completeness Principle). Include `Completeness: X/10` for each option. Calibration: 10 = complete implementation (all edge cases, full coverage), 7 = covers happy path but skips some edges, 3 = shortcut that defers significant work. If both options are 8+, pick the higher; if one is ≤5, flag it. +4. **Options:** Lettered options: `A) ... B) ... C) ...` — when an option involves effort, show both scales: `(human: ~X / CC: ~Y)` -If `_SESSIONS` is 3 or more: the user is juggling multiple gstack sessions and context-switching heavily. **ELI16 mode** — they may not remember what this conversation is about. Every AskUserQuestion MUST re-ground them: state the project, the branch, the current plan/task, then the specific problem, THEN the recommendation and options. Be extra clear and self-contained — assume they haven't looked at this window in 20 minutes. +Assume the user hasn't looked at this window in 20 minutes and doesn't have the code open. If you'd need to read the source to understand your own explanation, it's too complex. Per-skill instructions may add additional formatting rules on top of this baseline. +## Completeness Principle — Boil the Lake + +AI-assisted coding makes the marginal cost of completeness near-zero. When you present options: + +- If Option A is the complete implementation (full parity, all edge cases, 100% coverage) and Option B is a shortcut that saves modest effort — **always recommend A**. The delta between 80 lines and 150 lines is meaningless with CC+gstack. "Good enough" is the wrong instinct when "complete" costs minutes more. +- **Lake vs. ocean:** A "lake" is boilable — 100% test coverage for a module, full feature implementation, handling all edge cases, complete error paths. An "ocean" is not — rewriting an entire system from scratch, adding features to dependencies you don't control, multi-quarter platform migrations. Recommend boiling lakes. Flag oceans as out of scope. +- **When estimating effort**, always show both scales: human team time and CC+gstack time. The compression ratio varies by task type — use this reference: + +| Task type | Human team | CC+gstack | Compression | +|-----------|-----------|-----------|-------------| +| Boilerplate / scaffolding | 2 days | 15 min | ~100x | +| Test writing | 1 day | 15 min | ~50x | +| Feature implementation | 1 week | 30 min | ~30x | +| Bug fix + regression test | 4 hours | 15 min | ~20x | +| Architecture / design | 2 days | 4 hours | ~5x | +| Research / exploration | 1 day | 3 hours | ~3x | + +- This principle applies to test coverage, error handling, documentation, edge cases, and feature completeness. Don't skip the last 10% to "save time" — with AI, that 10% costs seconds. + +**Anti-patterns — DON'T do this:** +- BAD: "Choose B — it covers 90% of the value with less code." (If A is only 70 lines more, choose A.) +- BAD: "We can skip edge case handling to save time." (Edge case handling costs minutes with CC.) +- BAD: "Let's defer test coverage to a follow-up PR." (Tests are the cheapest lake to boil.) +- BAD: Quoting only human-team effort: "This would take 2 weeks." (Say: "2 weeks human / ~1 hour CC.") + ## Contributor Mode If `_CONTRIB` is `true`: you are in **contributor mode**. You're a gstack user who also helps make it better. diff --git a/ship/SKILL.md b/ship/SKILL.md index 0db81d8d..b0ab7756 100644 --- a/ship/SKILL.md +++ b/ship/SKILL.md @@ -181,7 +181,7 @@ After completing the review, read the review log and config to display the dashb ```bash eval $(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null) -cat ~/.gstack/projects/$SLUG/$BRANCH-reviews.jsonl 2>/dev/null || echo "NO_REVIEWS" +cat $PROJECTS_DIR/$SLUG/reviews/$BRANCH.jsonl 2>/dev/null || echo "NO_REVIEWS" echo "---CONFIG---" ~/.claude/skills/gstack/bin/gstack-config get skip_eng_review 2>/dev/null || echo "false" ``` @@ -218,7 +218,7 @@ If the Eng Review is NOT "CLEAR": 1. **Check for a prior override on this branch:** ```bash eval $(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null) - grep '"skill":"ship-review-override"' ~/.gstack/projects/$SLUG/$BRANCH-reviews.jsonl 2>/dev/null || echo "NO_OVERRIDE" + grep '"skill":"ship-review-override"' $PROJECTS_DIR/$SLUG/reviews/$BRANCH.jsonl 2>/dev/null || echo "NO_OVERRIDE" ``` If an override exists, display the dashboard and note "Review gate previously accepted — continuing." Do NOT ask again. @@ -232,7 +232,7 @@ If the Eng Review is NOT "CLEAR": 3. **If the user chooses A or C,** persist the decision so future `/ship` runs on this branch skip the gate: ```bash eval $(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null) - echo '{"skill":"ship-review-override","timestamp":"'"$(date -u +%Y-%m-%dT%H:%M:%SZ)"'","decision":"USER_CHOICE"}' >> ~/.gstack/projects/$SLUG/$BRANCH-reviews.jsonl + echo '{"skill":"ship-review-override","timestamp":"'"$(date -u +%Y-%m-%dT%H:%M:%SZ)"'","decision":"USER_CHOICE"}' >> $PROJECTS_DIR/$SLUG/reviews/$BRANCH.jsonl ``` Substitute USER_CHOICE with "ship_anyway" or "not_relevant". @@ -672,8 +672,8 @@ eval $(~/.claude/skills/gstack/bin/gstack-diff-scope 2>/dev/null) ```bash eval $(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null) -mkdir -p ~/.gstack/projects/$SLUG -echo '{"skill":"design-review-lite","timestamp":"TIMESTAMP","status":"STATUS","findings":N,"auto_fixed":M}' >> ~/.gstack/projects/$SLUG/$BRANCH-reviews.jsonl +mkdir -p $PROJECTS_DIR/$SLUG/reviews +echo '{"skill":"design-review-lite","timestamp":"TIMESTAMP","status":"STATUS","findings":N,"auto_fixed":M}' >> $PROJECTS_DIR/$SLUG/reviews/$BRANCH.jsonl ``` Substitute: TIMESTAMP = ISO 8601 datetime, STATUS = "clean" if 0 findings or "issues_found", N = total findings, M = auto-fixed count. diff --git a/test/gen-skill-docs.test.ts b/test/gen-skill-docs.test.ts index 9dfd1a1c..94cb4e23 100644 --- a/test/gen-skill-docs.test.ts +++ b/test/gen-skill-docs.test.ts @@ -329,14 +329,14 @@ describe('REVIEW_DASHBOARD resolver', () => { for (const skill of REVIEW_SKILLS) { test(`review dashboard appears in ${skill} generated file`, () => { const content = fs.readFileSync(path.join(ROOT, skill, 'SKILL.md'), 'utf-8'); - expect(content).toContain('reviews.jsonl'); + expect(content).toContain('$BRANCH.jsonl'); expect(content).toContain('REVIEW READINESS DASHBOARD'); }); } test('review dashboard appears in ship generated file', () => { const content = fs.readFileSync(path.join(ROOT, 'ship', 'SKILL.md'), 'utf-8'); - expect(content).toContain('reviews.jsonl'); + expect(content).toContain('$BRANCH.jsonl'); expect(content).toContain('REVIEW READINESS DASHBOARD'); }); diff --git a/test/skill-validation.test.ts b/test/skill-validation.test.ts index bd0e205b..bd845d92 100644 --- a/test/skill-validation.test.ts +++ b/test/skill-validation.test.ts @@ -262,26 +262,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('all project-scoped paths use gstack-slug (not REMOTE_SLUG)', () => { + // greptile-triage.md was the last file using REMOTE_SLUG — now uses gstack-slug + const triageContent = fs.readFileSync(path.join(ROOT, 'review', 'greptile-triage.md'), 'utf-8'); + expect(triageContent).toContain('gstack-slug'); + expect(triageContent).not.toContain('REMOTE_SLUG'); + expect(triageContent).toContain('$PROJECTS_DIR/$SLUG'); }); test('all greptile-history write references specify both per-project and global paths', () => { @@ -297,7 +283,7 @@ 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('$SLUG/greptile-history') && content.includes('~/.gstack/greptile-history')); expect(hasBoth).toBe(true); } @@ -305,7 +291,7 @@ describe('Cross-skill path consistency', () => { test('greptile-triage.md contains both project 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('$SLUG/greptile-history.md'); expect(content).toContain('~/.gstack/greptile-history.md'); }); @@ -313,7 +299,7 @@ describe('Cross-skill path consistency', () => { const content = fs.readFileSync(path.join(ROOT, 'retro', 'SKILL.md'), 'utf-8'); expect(content).toContain('~/.gstack/greptile-history.md'); // Should NOT reference per-project path for reads - expect(content).not.toContain('$REMOTE_SLUG/greptile-history.md'); + expect(content).not.toContain('$SLUG/greptile-history.md'); }); }); @@ -736,7 +722,7 @@ describe('CEO review mode validation', () => { }); test('has CEO plan persistence step', () => { - expect(content).toContain('ceo-plans'); + expect(content).toContain('plans/ceo'); expect(content).toContain('status: ACTIVE'); }); @@ -789,9 +775,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(/^PROJECTS_DIR=.+/); }); });