mirror of https://github.com/garrytan/gstack.git
Merge 13850ec388 into a3259400a3
This commit is contained in:
commit
a8d0f9ca0c
|
|
@ -1030,10 +1030,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-paths 2>/dev/null)" && eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" && PROJECT_DIR="$GSTACK_ARTIFACTS_ROOT/projects/$SLUG" && mkdir -p "$PROJECT_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_DIR/${BRANCH}-autoplan-restore-${DATETIME}.md"
|
||||
```
|
||||
|
||||
Write the plan file's full contents to the restore path with this header:
|
||||
|
|
|
|||
|
|
@ -169,7 +169,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_DIR/${BRANCH}-autoplan-restore-${DATETIME}.md"
|
||||
```
|
||||
|
||||
Write the plan file's full contents to the restore path with this header:
|
||||
|
|
|
|||
|
|
@ -63,6 +63,14 @@ CONFIG_HEADER='# gstack configuration — edit freely, changes take effect on ne
|
|||
# # See docs/designs/PLAN_TUNING_V1.md for rationale.
|
||||
#
|
||||
# ─── Artifacts sync (renamed from gbrain_sync_mode in v1.27.0.0) ─────
|
||||
# artifacts_root: state # state | repo
|
||||
# # state — write project artifacts under the gstack
|
||||
# # state root (default: ~/.gstack/projects)
|
||||
# # repo — write project artifacts under the current
|
||||
# # repo at .gstack/projects so authors can
|
||||
# # review/commit them with the project.
|
||||
# # Used by gstack-paths as GSTACK_ARTIFACTS_ROOT.
|
||||
#
|
||||
# artifacts_sync_mode: off # off | artifacts-only | full
|
||||
# # off — no sync (default)
|
||||
# # artifacts-only — sync plans/designs/retros/learnings only
|
||||
|
|
@ -126,6 +134,7 @@ lookup_default() {
|
|||
gstack_contributor) echo "false" ;;
|
||||
skip_eng_review) echo "false" ;;
|
||||
workspace_root) echo "$HOME/conductor/workspaces" ;;
|
||||
artifacts_root) echo "state" ;;
|
||||
cross_project_learnings) echo "" ;; # intentionally empty → unset triggers first-time prompt
|
||||
artifacts_sync_mode) echo "off" ;;
|
||||
artifacts_sync_mode_prompted) echo "false" ;;
|
||||
|
|
@ -299,6 +308,10 @@ case "${1:-}" in
|
|||
echo "Warning: artifacts_sync_mode '$VALUE' not recognized. Valid values: off, artifacts-only, full. Using off." >&2
|
||||
VALUE="off"
|
||||
fi
|
||||
if [ "$KEY" = "artifacts_root" ] && [ "$VALUE" != "state" ] && [ "$VALUE" != "repo" ]; then
|
||||
echo "Warning: artifacts_root '$VALUE' not recognized. Valid values: state, repo. Using state." >&2
|
||||
VALUE="state"
|
||||
fi
|
||||
# redact_repo_visibility: a LOCAL override for repos gh/glab can't read (e.g.
|
||||
# self-hosted GitLab). It lives in ~/.gstack/config.yaml (never committed), so
|
||||
# it can't be used to weaken the gate repo-wide for other contributors.
|
||||
|
|
@ -350,7 +363,7 @@ case "${1:-}" in
|
|||
for KEY in proactive routing_declined telemetry auto_upgrade update_check \
|
||||
skill_prefix checkpoint_mode checkpoint_push explain_level \
|
||||
codex_reviews gstack_contributor skip_eng_review workspace_root \
|
||||
artifacts_sync_mode artifacts_sync_mode_prompted plan_tune_hooks; do
|
||||
artifacts_root artifacts_sync_mode artifacts_sync_mode_prompted plan_tune_hooks; do
|
||||
VALUE=$(grep -E "^${KEY}:" "$CONFIG_FILE" 2>/dev/null | tail -1 | awk '{print $2}' | tr -d '[:space:]' || true)
|
||||
SOURCE="default"
|
||||
if [ -n "$VALUE" ]; then
|
||||
|
|
@ -366,7 +379,7 @@ case "${1:-}" in
|
|||
for KEY in proactive routing_declined telemetry auto_upgrade update_check \
|
||||
skill_prefix checkpoint_mode checkpoint_push explain_level \
|
||||
codex_reviews gstack_contributor skip_eng_review workspace_root \
|
||||
artifacts_sync_mode artifacts_sync_mode_prompted plan_tune_hooks; do
|
||||
artifacts_root artifacts_sync_mode artifacts_sync_mode_prompted plan_tune_hooks; do
|
||||
printf ' %-24s %s\n' "$KEY:" "$(lookup_default "$KEY")"
|
||||
done
|
||||
;;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
#!/usr/bin/env bash
|
||||
# gstack-paths — output portable state-root paths for skill bash blocks
|
||||
# Usage: eval "$(gstack-paths)" → sets GSTACK_STATE_ROOT, PLAN_ROOT, TMP_ROOT
|
||||
# Usage: eval "$(gstack-paths)" → sets GSTACK_STATE_ROOT, GSTACK_ARTIFACTS_ROOT, PLAN_ROOT, TMP_ROOT
|
||||
# Or: gstack-paths → prints GSTACK_STATE_ROOT=... etc.
|
||||
#
|
||||
# Resolves three roots with explicit fallback chains so skills work the same
|
||||
|
|
@ -9,9 +9,10 @@
|
|||
# CI / container env where HOME may be unset.
|
||||
#
|
||||
# Chains:
|
||||
# GSTACK_STATE_ROOT: GSTACK_HOME -> CLAUDE_PLUGIN_DATA (only when CLAUDE_PLUGIN_ROOT=*gstack*) -> $HOME/.gstack -> .gstack
|
||||
# PLAN_ROOT: GSTACK_PLAN_DIR -> CLAUDE_PLANS_DIR -> $HOME/.claude/plans -> .claude/plans
|
||||
# TMP_ROOT: TMPDIR -> TMP -> .gstack/tmp (and mkdir -p, best-effort)
|
||||
# GSTACK_STATE_ROOT: GSTACK_HOME -> CLAUDE_PLUGIN_DATA (only when CLAUDE_PLUGIN_ROOT=*gstack*) -> $HOME/.gstack -> .gstack
|
||||
# GSTACK_ARTIFACTS_ROOT: config artifacts_root=repo -> <git-root>/.gstack, otherwise GSTACK_STATE_ROOT
|
||||
# PLAN_ROOT: GSTACK_PLAN_DIR -> CLAUDE_PLANS_DIR -> $HOME/.claude/plans -> .claude/plans
|
||||
# TMP_ROOT: TMPDIR -> TMP -> .gstack/tmp (and mkdir -p, best-effort)
|
||||
#
|
||||
# Security: output values are not sanitized — callers may receive paths with
|
||||
# shell-special characters if env vars contain them. Skills should always quote
|
||||
|
|
@ -33,6 +34,24 @@ else
|
|||
_state_root=".gstack"
|
||||
fi
|
||||
|
||||
# Artifact root: where skills write user-facing project artifacts under
|
||||
# projects/$SLUG (design docs, review reports, test plans, checkpoints).
|
||||
# Defaults to the state root for backward compatibility. If the user sets
|
||||
# `artifacts_root: repo`, write those artifacts to the current repository's
|
||||
# `.gstack/` directory so they can be reviewed/committed with the project.
|
||||
_artifacts_root="$_state_root"
|
||||
_config_file="$_state_root/config.yaml"
|
||||
_artifacts_mode=""
|
||||
if [ -f "$_config_file" ]; then
|
||||
_artifacts_mode=$(grep -E '^artifacts_root:' "$_config_file" 2>/dev/null | tail -1 | awk '{print $2}' | tr -d '[:space:]' || true)
|
||||
fi
|
||||
if [ "$_artifacts_mode" = "repo" ]; then
|
||||
_repo_root=$(git rev-parse --show-toplevel 2>/dev/null || true)
|
||||
if [ -n "$_repo_root" ]; then
|
||||
_artifacts_root="$_repo_root/.gstack"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Plan root: where /context-save and /codex consult write plan files.
|
||||
if [ -n "${GSTACK_PLAN_DIR:-}" ]; then
|
||||
_plan_root="$GSTACK_PLAN_DIR"
|
||||
|
|
@ -61,5 +80,6 @@ fi
|
|||
mkdir -p "$_tmp_root" 2>/dev/null || true
|
||||
|
||||
echo "GSTACK_STATE_ROOT=$_state_root"
|
||||
echo "GSTACK_ARTIFACTS_ROOT=$_artifacts_root"
|
||||
echo "PLAN_ROOT=$_plan_root"
|
||||
echo "TMP_ROOT=$_tmp_root"
|
||||
|
|
|
|||
|
|
@ -1024,8 +1024,8 @@ Save report to `.gstack/canary-reports/{date}-canary.md` and `.gstack/canary-rep
|
|||
Log the result for the review dashboard:
|
||||
|
||||
```bash
|
||||
eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)"
|
||||
mkdir -p ~/.gstack/projects/$SLUG
|
||||
eval "$(~/.claude/skills/gstack/bin/gstack-paths 2>/dev/null)" && eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" && PROJECT_DIR="$GSTACK_ARTIFACTS_ROOT/projects/$SLUG"
|
||||
mkdir -p "$PROJECT_DIR"
|
||||
```
|
||||
|
||||
Write a JSONL entry: `{"skill":"canary","timestamp":"<ISO>","status":"<HEALTHY/DEGRADED/BROKEN>","url":"<url>","duration_min":<N>,"alerts":<N>}`
|
||||
|
|
|
|||
|
|
@ -198,7 +198,7 @@ Log the result for the review dashboard:
|
|||
|
||||
```bash
|
||||
{{SLUG_EVAL}}
|
||||
mkdir -p ~/.gstack/projects/$SLUG
|
||||
mkdir -p "$PROJECT_DIR"
|
||||
```
|
||||
|
||||
Write a JSONL entry: `{"skill":"canary","timestamp":"<ISO>","status":"<HEALTHY/DEGRADED/BROKEN>","url":"<url>","duration_min":<N>,"alerts":<N>}`
|
||||
|
|
|
|||
|
|
@ -817,9 +817,9 @@ Parse the user's input:
|
|||
### Step 1: Find saved contexts
|
||||
|
||||
```bash
|
||||
eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" && mkdir -p ~/.gstack/projects/$SLUG
|
||||
eval "$(~/.claude/skills/gstack/bin/gstack-paths 2>/dev/null)" && eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" && PROJECT_DIR="$GSTACK_ARTIFACTS_ROOT/projects/$SLUG" && mkdir -p "$PROJECT_DIR"
|
||||
eval "$(~/.claude/skills/gstack/bin/gstack-paths)"
|
||||
CHECKPOINT_DIR="$GSTACK_STATE_ROOT/projects/$SLUG/checkpoints"
|
||||
CHECKPOINT_DIR="$PROJECT_DIR/checkpoints"
|
||||
if [ ! -d "$CHECKPOINT_DIR" ]; then
|
||||
echo "NO_CHECKPOINTS"
|
||||
else
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ Parse the user's input:
|
|||
```bash
|
||||
{{SLUG_SETUP}}
|
||||
eval "$(~/.claude/skills/gstack/bin/gstack-paths)"
|
||||
CHECKPOINT_DIR="$GSTACK_STATE_ROOT/projects/$SLUG/checkpoints"
|
||||
CHECKPOINT_DIR="$PROJECT_DIR/checkpoints"
|
||||
if [ ! -d "$CHECKPOINT_DIR" ]; then
|
||||
echo "NO_CHECKPOINTS"
|
||||
else
|
||||
|
|
|
|||
|
|
@ -812,7 +812,7 @@ If the user types `/context-save resume` or `/context-save restore`, tell them:
|
|||
### Step 1: Gather state
|
||||
|
||||
```bash
|
||||
eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" && mkdir -p ~/.gstack/projects/$SLUG
|
||||
eval "$(~/.claude/skills/gstack/bin/gstack-paths 2>/dev/null)" && eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" && PROJECT_DIR="$GSTACK_ARTIFACTS_ROOT/projects/$SLUG" && mkdir -p "$PROJECT_DIR"
|
||||
```
|
||||
|
||||
Collect the current working state:
|
||||
|
|
@ -872,9 +872,9 @@ inject shell metacharacters into any subsequent command. The sanitizer is an
|
|||
allowlist: only `a-z 0-9 - .` survive.
|
||||
|
||||
```bash
|
||||
eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" && mkdir -p ~/.gstack/projects/$SLUG
|
||||
eval "$(~/.claude/skills/gstack/bin/gstack-paths 2>/dev/null)" && eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" && PROJECT_DIR="$GSTACK_ARTIFACTS_ROOT/projects/$SLUG" && mkdir -p "$PROJECT_DIR"
|
||||
eval "$(~/.claude/skills/gstack/bin/gstack-paths)"
|
||||
CHECKPOINT_DIR="$GSTACK_STATE_ROOT/projects/$SLUG/checkpoints"
|
||||
CHECKPOINT_DIR="$PROJECT_DIR/checkpoints"
|
||||
mkdir -p "$CHECKPOINT_DIR"
|
||||
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
|
||||
# Bash-side title sanitize. Pass the raw title as $1 when running this block.
|
||||
|
|
@ -959,9 +959,9 @@ Restore later with /context-restore.
|
|||
### Step 1: Gather saved contexts
|
||||
|
||||
```bash
|
||||
eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" && mkdir -p ~/.gstack/projects/$SLUG
|
||||
eval "$(~/.claude/skills/gstack/bin/gstack-paths 2>/dev/null)" && eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" && PROJECT_DIR="$GSTACK_ARTIFACTS_ROOT/projects/$SLUG" && mkdir -p "$PROJECT_DIR"
|
||||
eval "$(~/.claude/skills/gstack/bin/gstack-paths)"
|
||||
CHECKPOINT_DIR="$GSTACK_STATE_ROOT/projects/$SLUG/checkpoints"
|
||||
CHECKPOINT_DIR="$PROJECT_DIR/checkpoints"
|
||||
if [ -d "$CHECKPOINT_DIR" ]; then
|
||||
echo "CHECKPOINT_DIR=$CHECKPOINT_DIR"
|
||||
# Use find + sort instead of ls -1t: filename YYYYMMDD-HHMMSS prefix is the
|
||||
|
|
|
|||
|
|
@ -119,7 +119,7 @@ allowlist: only `a-z 0-9 - .` survive.
|
|||
```bash
|
||||
{{SLUG_SETUP}}
|
||||
eval "$(~/.claude/skills/gstack/bin/gstack-paths)"
|
||||
CHECKPOINT_DIR="$GSTACK_STATE_ROOT/projects/$SLUG/checkpoints"
|
||||
CHECKPOINT_DIR="$PROJECT_DIR/checkpoints"
|
||||
mkdir -p "$CHECKPOINT_DIR"
|
||||
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
|
||||
# Bash-side title sanitize. Pass the raw title as $1 when running this block.
|
||||
|
|
@ -206,7 +206,7 @@ Restore later with /context-restore.
|
|||
```bash
|
||||
{{SLUG_SETUP}}
|
||||
eval "$(~/.claude/skills/gstack/bin/gstack-paths)"
|
||||
CHECKPOINT_DIR="$GSTACK_STATE_ROOT/projects/$SLUG/checkpoints"
|
||||
CHECKPOINT_DIR="$PROJECT_DIR/checkpoints"
|
||||
if [ -d "$CHECKPOINT_DIR" ]; then
|
||||
echo "CHECKPOINT_DIR=$CHECKPOINT_DIR"
|
||||
# Use find + sort instead of ls -1t: filename YYYYMMDD-HHMMSS prefix is the
|
||||
|
|
|
|||
|
|
@ -853,7 +853,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)"
|
||||
eval "$(~/.claude/skills/gstack/bin/gstack-paths 2>/dev/null)" && eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" && PROJECT_DIR="$GSTACK_ARTIFACTS_ROOT/projects/$SLUG"
|
||||
ls ~/.gstack/projects/$SLUG/*office-hours* 2>/dev/null | head -5
|
||||
ls .context/*office-hours* .context/attachments/*office-hours* 2>/dev/null | head -5
|
||||
```
|
||||
|
|
|
|||
|
|
@ -1925,9 +1925,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-paths 2>/dev/null)" && eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" && PROJECT_DIR="$GSTACK_ARTIFACTS_ROOT/projects/$SLUG" && mkdir -p "$PROJECT_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 `$PROJECT_DIR/{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
|
||||
|
|
|
|||
|
|
@ -268,7 +268,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 `$PROJECT_DIR/{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
|
||||
|
|
|
|||
|
|
@ -985,10 +985,10 @@ DETAILS: Lint (3 warnings)
|
|||
## Step 5: Persist to Health History
|
||||
|
||||
```bash
|
||||
eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" && mkdir -p ~/.gstack/projects/$SLUG
|
||||
eval "$(~/.claude/skills/gstack/bin/gstack-paths 2>/dev/null)" && eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" && PROJECT_DIR="$GSTACK_ARTIFACTS_ROOT/projects/$SLUG" && mkdir -p "$PROJECT_DIR"
|
||||
```
|
||||
|
||||
Append one JSONL line to `~/.gstack/projects/$SLUG/health-history.jsonl`:
|
||||
Append one JSONL line to `$PROJECT_DIR/health-history.jsonl`:
|
||||
|
||||
```json
|
||||
{"ts":"2026-03-31T14:30:00Z","branch":"main","score":9.1,"typecheck":10,"lint":8,"test":10,"deadcode":7,"shell":10,"gbrain":10,"duration_s":23}
|
||||
|
|
@ -1009,12 +1009,12 @@ and start new tracking from the first post-D6 run.
|
|||
|
||||
## Step 6: Trend Analysis + Recommendations
|
||||
|
||||
Read the last 10 entries from `~/.gstack/projects/$SLUG/health-history.jsonl` (if the
|
||||
Read the last 10 entries from `$PROJECT_DIR/health-history.jsonl` (if the
|
||||
file exists and has prior entries).
|
||||
|
||||
```bash
|
||||
eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" && mkdir -p ~/.gstack/projects/$SLUG
|
||||
tail -10 ~/.gstack/projects/$SLUG/health-history.jsonl 2>/dev/null || echo "NO_HISTORY"
|
||||
eval "$(~/.claude/skills/gstack/bin/gstack-paths 2>/dev/null)" && eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" && PROJECT_DIR="$GSTACK_ARTIFACTS_ROOT/projects/$SLUG" && mkdir -p "$PROJECT_DIR"
|
||||
tail -10 "$PROJECT_DIR/health-history.jsonl" 2>/dev/null || echo "NO_HISTORY"
|
||||
```
|
||||
|
||||
**If prior entries exist, show the trend:**
|
||||
|
|
|
|||
|
|
@ -232,7 +232,7 @@ DETAILS: Lint (3 warnings)
|
|||
{{SLUG_SETUP}}
|
||||
```
|
||||
|
||||
Append one JSONL line to `~/.gstack/projects/$SLUG/health-history.jsonl`:
|
||||
Append one JSONL line to `$PROJECT_DIR/health-history.jsonl`:
|
||||
|
||||
```json
|
||||
{"ts":"2026-03-31T14:30:00Z","branch":"main","score":9.1,"typecheck":10,"lint":8,"test":10,"deadcode":7,"shell":10,"gbrain":10,"duration_s":23}
|
||||
|
|
@ -253,12 +253,12 @@ and start new tracking from the first post-D6 run.
|
|||
|
||||
## Step 6: Trend Analysis + Recommendations
|
||||
|
||||
Read the last 10 entries from `~/.gstack/projects/$SLUG/health-history.jsonl` (if the
|
||||
Read the last 10 entries from `$PROJECT_DIR/health-history.jsonl` (if the
|
||||
file exists and has prior entries).
|
||||
|
||||
```bash
|
||||
{{SLUG_SETUP}}
|
||||
tail -10 ~/.gstack/projects/$SLUG/health-history.jsonl 2>/dev/null || echo "NO_HISTORY"
|
||||
tail -10 "$PROJECT_DIR/health-history.jsonl" 2>/dev/null || echo "NO_HISTORY"
|
||||
```
|
||||
|
||||
**If prior entries exist, show the trend:**
|
||||
|
|
|
|||
|
|
@ -954,12 +954,12 @@ Check whether this project has been through a successful `/land-and-deploy` befo
|
|||
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
|
||||
eval "$(~/.claude/skills/gstack/bin/gstack-paths 2>/dev/null)" && eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" && PROJECT_DIR="$GSTACK_ARTIFACTS_ROOT/projects/$SLUG"
|
||||
if [ ! -f "$PROJECT_DIR/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_DIR/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)
|
||||
|
|
@ -1153,10 +1153,10 @@ Present the full dry-run results to the user via AskUserQuestion:
|
|||
|
||||
Save the deploy config fingerprint so we can detect future changes:
|
||||
```bash
|
||||
mkdir -p ~/.gstack/projects/$SLUG
|
||||
mkdir -p "$PROJECT_DIR"
|
||||
CURRENT_HASH=$(sed -n '/## Deploy Configuration/,/^## /p' CLAUDE.md 2>/dev/null | shasum -a 256 | cut -d' ' -f1)
|
||||
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)
|
||||
echo "${CURRENT_HASH}-${WORKFLOW_HASH}" > ~/.gstack/projects/$SLUG/land-deploy-confirmed
|
||||
echo "${CURRENT_HASH}-${WORKFLOW_HASH}" > "$PROJECT_DIR/land-deploy-confirmed"
|
||||
```
|
||||
Continue to Step 2.
|
||||
|
||||
|
|
@ -1875,8 +1875,8 @@ Save report to `.gstack/deploy-reports/{date}-pr{number}-deploy.md`.
|
|||
Log to the review dashboard:
|
||||
|
||||
```bash
|
||||
eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)"
|
||||
mkdir -p ~/.gstack/projects/$SLUG
|
||||
eval "$(~/.claude/skills/gstack/bin/gstack-paths 2>/dev/null)" && eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" && PROJECT_DIR="$GSTACK_ARTIFACTS_ROOT/projects/$SLUG"
|
||||
mkdir -p "$PROJECT_DIR"
|
||||
```
|
||||
|
||||
Write a JSONL entry with timing data:
|
||||
|
|
|
|||
|
|
@ -111,11 +111,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_DIR/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_DIR/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)
|
||||
|
|
@ -276,10 +276,10 @@ Present the full dry-run results to the user via AskUserQuestion:
|
|||
|
||||
Save the deploy config fingerprint so we can detect future changes:
|
||||
```bash
|
||||
mkdir -p ~/.gstack/projects/$SLUG
|
||||
mkdir -p "$PROJECT_DIR"
|
||||
CURRENT_HASH=$(sed -n '/## Deploy Configuration/,/^## /p' CLAUDE.md 2>/dev/null | shasum -a 256 | cut -d' ' -f1)
|
||||
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)
|
||||
echo "${CURRENT_HASH}-${WORKFLOW_HASH}" > ~/.gstack/projects/$SLUG/land-deploy-confirmed
|
||||
echo "${CURRENT_HASH}-${WORKFLOW_HASH}" > "$PROJECT_DIR/land-deploy-confirmed"
|
||||
```
|
||||
Continue to Step 2.
|
||||
|
||||
|
|
@ -966,7 +966,7 @@ Log to the review dashboard:
|
|||
|
||||
```bash
|
||||
{{SLUG_EVAL}}
|
||||
mkdir -p ~/.gstack/projects/$SLUG
|
||||
mkdir -p "$PROJECT_DIR"
|
||||
```
|
||||
|
||||
Write a JSONL entry with timing data:
|
||||
|
|
|
|||
|
|
@ -922,7 +922,7 @@ rm -f /tmp/.gstack-brain-context-$$.md 2>/dev/null || true
|
|||
Understand the project and the area the user wants to change.
|
||||
|
||||
```bash
|
||||
eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)"
|
||||
eval "$(~/.claude/skills/gstack/bin/gstack-paths 2>/dev/null)" && eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" && PROJECT_DIR="$GSTACK_ARTIFACTS_ROOT/projects/$SLUG"
|
||||
```
|
||||
|
||||
1. Read `CLAUDE.md`, `TODOS.md` (if they exist).
|
||||
|
|
@ -931,7 +931,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_DIR"/*-design-*.md 2>/dev/null
|
||||
```
|
||||
If design docs exist, list them: "Prior designs for this project: [titles + dates]"
|
||||
|
||||
|
|
@ -1228,7 +1228,7 @@ 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 "<keyword1>\|<keyword2>\|<keyword3>" ~/.gstack/projects/$SLUG/*-design-*.md 2>/dev/null
|
||||
grep -li "<keyword1>\|<keyword2>\|<keyword3>" "$PROJECT_DIR"/*-design-*.md 2>/dev/null
|
||||
```
|
||||
|
||||
If matches found, read the matching design docs and surface them:
|
||||
|
|
|
|||
|
|
@ -87,7 +87,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_DIR"/*-design-*.md 2>/dev/null
|
||||
```
|
||||
If design docs exist, list them: "Prior designs for this project: [titles + dates]"
|
||||
|
||||
|
|
@ -341,7 +341,7 @@ 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 "<keyword1>\|<keyword2>\|<keyword3>" ~/.gstack/projects/$SLUG/*-design-*.md 2>/dev/null
|
||||
grep -li "<keyword1>\|<keyword2>\|<keyword3>" "$PROJECT_DIR"/*-design-*.md 2>/dev/null
|
||||
```
|
||||
|
||||
If matches found, read the matching design docs and surface them:
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
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-paths 2>/dev/null)" && eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" && PROJECT_DIR="$GSTACK_ARTIFACTS_ROOT/projects/$SLUG" && mkdir -p "$PROJECT_DIR"
|
||||
USER=$(whoami)
|
||||
DATETIME=$(date +%Y%m%d-%H%M%S)
|
||||
```
|
||||
|
|
@ -13,11 +13,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_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 `$PROJECT_DIR/{user}-{branch}-design-{datetime}.md`.
|
||||
|
||||
After writing the design doc, tell the user:
|
||||
**"Design doc saved to: {full path}. Other skills (/plan-ceo-review, /plan-eng-review) will find it automatically."**
|
||||
|
|
|
|||
|
|
@ -11,11 +11,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_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 `$PROJECT_DIR/{user}-{branch}-design-{datetime}.md`.
|
||||
|
||||
After writing the design doc, tell the user:
|
||||
**"Design doc saved to: {full path}. Other skills (/plan-ceo-review, /plan-eng-review) will find it automatically."**
|
||||
|
|
|
|||
|
|
@ -947,10 +947,11 @@ Then read CLAUDE.md, TODOS.md, and any existing architecture docs.
|
|||
**Design doc check:**
|
||||
```bash
|
||||
setopt +o nomatch 2>/dev/null || true # zsh compat
|
||||
eval "$(~/.claude/skills/gstack/bin/gstack-paths 2>/dev/null)" && eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" && PROJECT_DIR="$GSTACK_ARTIFACTS_ROOT/projects/$SLUG"
|
||||
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_DIR"/*-$BRANCH-design-*.md 2>/dev/null | head -1)
|
||||
[ -z "$DESIGN" ] && DESIGN=$(ls -t "$PROJECT_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.
|
||||
|
|
@ -1288,7 +1289,8 @@ eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" && mkdir -p ~/.gst
|
|||
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
|
||||
eval "$(~/.claude/skills/gstack/bin/gstack-paths 2>/dev/null)" && eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" && PROJECT_DIR="$GSTACK_ARTIFACTS_ROOT/projects/$SLUG" && mkdir -p "$PROJECT_DIR"
|
||||
mkdir -p "$PROJECT_DIR/ceo-plans/archive"
|
||||
# For each stale plan: mv ~/.gstack/projects/$SLUG/ceo-plans/{old-plan}.md ~/.gstack/projects/$SLUG/ceo-plans/archive/
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -136,10 +136,11 @@ 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_EVAL}}
|
||||
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_DIR"/*-$BRANCH-design-*.md 2>/dev/null | head -1)
|
||||
[ -z "$DESIGN" ] && DESIGN=$(ls -t "$PROJECT_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.
|
||||
|
|
@ -327,7 +328,8 @@ eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" && mkdir -p ~/.gst
|
|||
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
|
||||
{{SLUG_SETUP}}
|
||||
mkdir -p "$PROJECT_DIR/ceo-plans/archive"
|
||||
# For each stale plan: mv ~/.gstack/projects/$SLUG/ceo-plans/{old-plan}.md ~/.gstack/projects/$SLUG/ceo-plans/archive/
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -591,8 +591,8 @@ 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
|
||||
eval "$(~/.claude/skills/gstack/bin/gstack-paths 2>/dev/null)" && eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" && PROJECT_DIR="$GSTACK_ARTIFACTS_ROOT/projects/$SLUG"
|
||||
rm -f "$PROJECT_DIR"/*-$BRANCH-ceo-handoff-*.md 2>/dev/null || true
|
||||
```
|
||||
|
||||
## Review Log
|
||||
|
|
|
|||
|
|
@ -376,7 +376,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_DIR"/*-$BRANCH-ceo-handoff-*.md 2>/dev/null || true
|
||||
```
|
||||
|
||||
## Review Log
|
||||
|
|
|
|||
|
|
@ -971,10 +971,11 @@ Then read:
|
|||
**Design doc check:**
|
||||
```bash
|
||||
setopt +o nomatch 2>/dev/null || true
|
||||
eval "$(~/.claude/skills/gstack/bin/gstack-paths 2>/dev/null)" && eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" && PROJECT_DIR="$GSTACK_ARTIFACTS_ROOT/projects/$SLUG"
|
||||
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_DIR"/*-$BRANCH-design-*.md 2>/dev/null | head -1)
|
||||
[ -z "$DESIGN" ] && DESIGN=$(ls -t "$PROJECT_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.
|
||||
|
|
|
|||
|
|
@ -97,10 +97,11 @@ Then read:
|
|||
**Design doc check:**
|
||||
```bash
|
||||
setopt +o nomatch 2>/dev/null || true
|
||||
{{SLUG_EVAL}}
|
||||
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_DIR"/*-$BRANCH-design-*.md 2>/dev/null | head -1)
|
||||
[ -z "$DESIGN" ] && DESIGN=$(ls -t "$PROJECT_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.
|
||||
|
|
|
|||
|
|
@ -911,10 +911,11 @@ sections. Read a section in full before doing its step; do not work from memory.
|
|||
### Design Doc Check
|
||||
```bash
|
||||
setopt +o nomatch 2>/dev/null || true # zsh compat
|
||||
eval "$(~/.claude/skills/gstack/bin/gstack-paths 2>/dev/null)" && eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" && PROJECT_DIR="$GSTACK_ARTIFACTS_ROOT/projects/$SLUG"
|
||||
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_DIR"/*-$BRANCH-design-*.md 2>/dev/null | head -1)
|
||||
[ -z "$DESIGN" ] && DESIGN=$(ls -t "$PROJECT_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.
|
||||
|
|
|
|||
|
|
@ -102,10 +102,11 @@ 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_EVAL}}
|
||||
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_DIR"/*-$BRANCH-design-*.md 2>/dev/null | head -1)
|
||||
[ -z "$DESIGN" ] && DESIGN=$(ls -t "$PROJECT_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.
|
||||
|
|
|
|||
|
|
@ -908,8 +908,8 @@ 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
|
||||
```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
|
||||
eval "$(~/.claude/skills/gstack/bin/gstack-paths 2>/dev/null)" && eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" && PROJECT_DIR="$GSTACK_ARTIFACTS_ROOT/projects/$SLUG"
|
||||
ls -t "$PROJECT_DIR"/*-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.
|
||||
|
|
@ -1204,9 +1204,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-paths 2>/dev/null)" && eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" && PROJECT_DIR="$GSTACK_ARTIFACTS_ROOT/projects/$SLUG" && mkdir -p "$PROJECT_DIR"
|
||||
```
|
||||
Write to `~/.gstack/projects/{slug}/{user}-{branch}-test-outcome-{datetime}.md`
|
||||
Write to `$PROJECT_DIR/{user}-{branch}-test-outcome-{datetime}.md`
|
||||
|
||||
### Output Structure
|
||||
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ Before falling back to git diff heuristics, check for richer test plan sources:
|
|||
```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_DIR"/*-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.
|
||||
|
|
@ -87,7 +87,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 `$PROJECT_DIR/{user}-{branch}-test-outcome-{datetime}.md`
|
||||
|
||||
### Output Structure
|
||||
|
||||
|
|
|
|||
|
|
@ -1140,8 +1140,8 @@ 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
|
||||
```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
|
||||
eval "$(~/.claude/skills/gstack/bin/gstack-paths 2>/dev/null)" && eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" && PROJECT_DIR="$GSTACK_ARTIFACTS_ROOT/projects/$SLUG"
|
||||
ls -t "$PROJECT_DIR"/*-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.
|
||||
|
|
@ -1618,9 +1618,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-paths 2>/dev/null)" && eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" && PROJECT_DIR="$GSTACK_ARTIFACTS_ROOT/projects/$SLUG" && mkdir -p "$PROJECT_DIR"
|
||||
```
|
||||
Write to `~/.gstack/projects/{slug}/{user}-{branch}-test-outcome-{datetime}.md`
|
||||
Write to `$PROJECT_DIR/{user}-{branch}-test-outcome-{datetime}.md`
|
||||
|
||||
**Per-issue additions** (beyond standard report template):
|
||||
- Fix Status: verified / best-effort / reverted / deferred
|
||||
|
|
|
|||
|
|
@ -110,7 +110,7 @@ Before falling back to git diff heuristics, check for richer test plan sources:
|
|||
```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_DIR"/*-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.
|
||||
|
|
@ -313,7 +313,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 `$PROJECT_DIR/{user}-{branch}-test-outcome-{datetime}.md`
|
||||
|
||||
**Per-issue additions** (beyond standard report template):
|
||||
- Fix Status: verified / best-effort / reverted / deferred
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import type { TemplateContext } from './types';
|
||||
|
||||
export function generateSlugEval(ctx: TemplateContext): string {
|
||||
return `eval "$(${ctx.paths.binDir}/gstack-slug 2>/dev/null)"`;
|
||||
return `eval "$(${ctx.paths.binDir}/gstack-paths 2>/dev/null)" && eval "$(${ctx.paths.binDir}/gstack-slug 2>/dev/null)" && PROJECT_DIR="$GSTACK_ARTIFACTS_ROOT/projects/$SLUG"`;
|
||||
}
|
||||
|
||||
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-paths 2>/dev/null)" && eval "$(${ctx.paths.binDir}/gstack-slug 2>/dev/null)" && PROJECT_DIR="$GSTACK_ARTIFACTS_ROOT/projects/$SLUG" && mkdir -p "$PROJECT_DIR"`;
|
||||
}
|
||||
|
||||
export function generateBaseBranchDetect(_ctx: TemplateContext): string {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
import { describe, test, expect } from 'bun:test';
|
||||
import { spawnSync } from 'child_process';
|
||||
import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
|
||||
const ROOT = path.resolve(import.meta.dir, '..');
|
||||
|
|
@ -15,10 +17,11 @@ const BIN = path.join(ROOT, 'bin', 'gstack-paths');
|
|||
// HOME from USERPROFILE at shell startup if HOME is unset/empty, which
|
||||
// silently breaks the "HOME unset" test scenarios. Clearing USERPROFILE
|
||||
// alongside HOME prevents that auto-population on Windows runners.
|
||||
function run(env: Record<string, string | undefined>): Record<string, string> {
|
||||
function run(env: Record<string, string | undefined>, cwd?: string): Record<string, string> {
|
||||
const result = spawnSync('bash', [BIN], {
|
||||
env: { PATH: process.env.PATH, USERPROFILE: '', ...env } as Record<string, string>,
|
||||
encoding: 'utf-8',
|
||||
cwd,
|
||||
});
|
||||
if (result.status !== 0) {
|
||||
throw new Error(`gstack-paths failed (status ${result.status}): ${result.stderr}`);
|
||||
|
|
@ -100,10 +103,53 @@ describe('gstack-paths', () => {
|
|||
test('emits all three exports on every invocation', () => {
|
||||
const got = run({ HOME: '/tmp/h' });
|
||||
expect(got).toHaveProperty('GSTACK_STATE_ROOT');
|
||||
expect(got).toHaveProperty('GSTACK_ARTIFACTS_ROOT');
|
||||
expect(got).toHaveProperty('PLAN_ROOT');
|
||||
expect(got).toHaveProperty('TMP_ROOT');
|
||||
});
|
||||
|
||||
test('artifacts_root=repo writes skill artifacts under the current repo', () => {
|
||||
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'gstack-artifacts-root-'));
|
||||
const home = path.join(tmp, 'home');
|
||||
const repo = path.join(tmp, 'repo');
|
||||
fs.mkdirSync(path.join(home, '.gstack'), { recursive: true });
|
||||
fs.mkdirSync(repo, { recursive: true });
|
||||
fs.writeFileSync(path.join(home, '.gstack', 'config.yaml'), 'artifacts_root: repo\n');
|
||||
spawnSync('git', ['init'], { cwd: repo, encoding: 'utf-8' });
|
||||
|
||||
const got = run({ HOME: home }, repo);
|
||||
|
||||
expect(got.GSTACK_STATE_ROOT).toBe(path.join(home, '.gstack'));
|
||||
expect(got.GSTACK_ARTIFACTS_ROOT).toBe(path.join(fs.realpathSync(repo), '.gstack'));
|
||||
});
|
||||
|
||||
test('SLUG_SETUP uses GSTACK_ARTIFACTS_ROOT instead of hardcoded ~/.gstack', async () => {
|
||||
const { generateSlugEval, generateSlugSetup } = await import('../scripts/resolvers/utility');
|
||||
const ctx = {
|
||||
skillName: 'test-skill',
|
||||
tmplPath: 'test/SKILL.md.tmpl',
|
||||
host: 'claude',
|
||||
paths: {
|
||||
skillRoot: '~/.claude/skills/gstack',
|
||||
localSkillRoot: '.claude/skills/gstack',
|
||||
binDir: '~/.claude/skills/gstack/bin',
|
||||
browseDir: '~/.claude/skills/gstack/browse/dist',
|
||||
designDir: '~/.claude/skills/gstack/design/dist',
|
||||
makePdfDir: '~/.claude/skills/gstack/make-pdf/dist',
|
||||
},
|
||||
} as const;
|
||||
const rendered = generateSlugSetup({
|
||||
...ctx,
|
||||
});
|
||||
const evalRendered = generateSlugEval({ ...ctx });
|
||||
|
||||
expect(rendered).toContain('gstack-paths');
|
||||
expect(rendered).toContain('"$GSTACK_ARTIFACTS_ROOT/projects/$SLUG"');
|
||||
expect(evalRendered).toContain('gstack-paths');
|
||||
expect(evalRendered).toContain('PROJECT_DIR="$GSTACK_ARTIFACTS_ROOT/projects/$SLUG"');
|
||||
expect(rendered).not.toContain('~/.gstack/projects/$SLUG');
|
||||
});
|
||||
|
||||
test('output is shell-evalable: only KEY=VALUE lines, no extra prose', () => {
|
||||
const result = spawnSync('bash', [BIN], {
|
||||
env: { PATH: process.env.PATH, USERPROFILE: '', HOME: '/tmp/h' } as Record<string, string>,
|
||||
|
|
|
|||
|
|
@ -121,3 +121,43 @@ describe('gstack-config: plan_tune_hooks key', () => {
|
|||
expect(got).toBe('prompt');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('gstack-config: artifacts_root key', () => {
|
||||
let tmpHome: string;
|
||||
let env: NodeJS.ProcessEnv;
|
||||
|
||||
beforeAll(() => {
|
||||
tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), 'gstack-artifacts-root-cfg-'));
|
||||
env = { ...process.env, GSTACK_HOME: tmpHome };
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
fs.rmSync(tmpHome, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
test('defaults to state and appears in defaults/list output', () => {
|
||||
const out = execSync(`${GSTACK_CONFIG} get artifacts_root`, {
|
||||
encoding: 'utf-8',
|
||||
env,
|
||||
}).trim();
|
||||
expect(out).toBe('state');
|
||||
const defaults = execSync(`${GSTACK_CONFIG} defaults`, { encoding: 'utf-8', env });
|
||||
expect(defaults).toContain('artifacts_root');
|
||||
const list = execSync(`${GSTACK_CONFIG} list`, { encoding: 'utf-8', env });
|
||||
expect(list).toContain('artifacts_root');
|
||||
});
|
||||
|
||||
test('accepts state/repo and rejects out-of-domain values', () => {
|
||||
for (const v of ['state', 'repo']) {
|
||||
execSync(`${GSTACK_CONFIG} set artifacts_root ${v}`, { encoding: 'utf-8', env });
|
||||
const got = execSync(`${GSTACK_CONFIG} get artifacts_root`, { encoding: 'utf-8', env }).trim();
|
||||
expect(got).toBe(v);
|
||||
}
|
||||
|
||||
const res = execSync(`${GSTACK_CONFIG} set artifacts_root elsewhere 2>&1`, { encoding: 'utf-8', env });
|
||||
expect(res.toLowerCase()).toContain('not recognized');
|
||||
const got = execSync(`${GSTACK_CONFIG} get artifacts_root`, { encoding: 'utf-8', env }).trim();
|
||||
expect(got).toBe('state');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue