mirror of
https://github.com/garrytan/gstack.git
synced 2026-05-27 05:22:29 +02:00
feat: design taste engine with persistent schema
Adds a cross-session taste profile that learns from design-shotgun
approval/rejection decisions. Biases future design-consultation and
design-shotgun proposals toward the user's demonstrated preferences.
Codex review caught that the plan had "taste engine" as a vague goal
without schema, decay, migration, or placeholder insertion points. This
commit ships the full spec.
Schema v1 at ~/.gstack/projects/$SLUG/taste-profile.json:
- version, updated_at
- dimensions: fonts, colors, layouts, aesthetics — each with approved[]
and rejected[] preference lists
- sessions: last 50 (FIFO truncation), each with ts/action/variant/reason
- Preference: { value, confidence, approved_count, rejected_count, last_seen }
- Confidence: Laplace-smoothed approved/(total+1)
- Decay: 5% per week of inactivity, computed at read time (not write)
Changes:
- bin/gstack-taste-update: new CLI. Subcommands approved/rejected/show/
migrate. Parses reason string for dimension signals (e.g.,
"fonts: Geist; colors: slate; aesthetics: minimal"). Emits taste-drift
NOTE when a new signal contradicts a strong opposing signal. Legacy
approved.json aggregates migrate to v1 on next write.
- scripts/resolvers/design.ts: new generateTasteProfile() resolver.
Produces the prose that skills see: how to read the profile, how to
factor into proposals, conflict handling, schema migration.
- scripts/resolvers/index.ts: register TASTE_PROFILE and a BIN_DIR
resolver (returns ctx.paths.binDir, used by templates that shell out
to gstack-* binaries).
- design-consultation/SKILL.md.tmpl: insert {{TASTE_PROFILE}} placeholder
in Phase 1 right after the memorable-thing forcing question so the
Phase 3 proposal can factor in learned preferences.
- design-shotgun/SKILL.md.tmpl: taste memory section now reads
taste-profile.json via {{TASTE_PROFILE}}, falls back to per-session
approved.json (legacy). Approval flow documented to call
gstack-taste-update after user picks/rejects a variant.
Known gap: v1 extracts dimension signals from a reason string passed
by the caller ("fonts: X; colors: Y"). Future v2 can read EXIF or an
accompanying manifest written by design-shotgun alongside each variant
for automatic dimension extraction without needing the reason argument.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+54
-6
@@ -785,7 +785,52 @@ Two rounds max of context gathering, then proceed with what you have and note as
|
||||
|
||||
## Step 2: Taste Memory
|
||||
|
||||
Read prior approved designs to bias generation toward the user's demonstrated taste:
|
||||
Read both the persistent taste profile (cross-session) AND the per-session approved
|
||||
designs to bias generation toward the user's demonstrated taste.
|
||||
|
||||
**Persistent taste profile (v1 schema at `~/.gstack/projects/$SLUG/taste-profile.json`):**
|
||||
|
||||
Read the persistent taste profile if it exists:
|
||||
|
||||
```bash
|
||||
_TASTE_PROFILE=~/.gstack/projects/$SLUG/taste-profile.json
|
||||
if [ -f "$_TASTE_PROFILE" ]; then
|
||||
# Schema v1: { dimensions: { fonts, colors, layouts, aesthetics }, sessions: [] }
|
||||
# Each dimension has approved[] and rejected[] entries with
|
||||
# { value, confidence, approved_count, rejected_count, last_seen }
|
||||
# Confidence decays 5% per week of inactivity — computed at read time.
|
||||
cat "$_TASTE_PROFILE" 2>/dev/null | head -200
|
||||
echo "TASTE_PROFILE_FOUND"
|
||||
else
|
||||
echo "NO_TASTE_PROFILE"
|
||||
fi
|
||||
```
|
||||
|
||||
**If TASTE_PROFILE_FOUND:** Summarize the strongest signals (top 3 approved entries
|
||||
per dimension by confidence * approved_count). Include them in the design brief:
|
||||
|
||||
"Based on \${SESSION_COUNT} prior sessions, this user's taste leans toward:
|
||||
fonts [top-3], colors [top-3], layouts [top-3], aesthetics [top-3]. Bias
|
||||
generation toward these unless the user explicitly requests a different direction.
|
||||
Also avoid their strong rejections: [top-3 rejected per dimension]."
|
||||
|
||||
**If NO_TASTE_PROFILE:** Fall through to per-session approved.json files (legacy).
|
||||
|
||||
**Conflict handling:** If the current user request contradicts a strong persistent
|
||||
signal (e.g., "make it playful" when taste profile strongly prefers minimal), flag
|
||||
it: "Note: your taste profile strongly prefers minimal. You're asking for playful
|
||||
this time — I'll proceed, but want me to update the taste profile, or treat this
|
||||
as a one-off?"
|
||||
|
||||
**Decay:** Confidence scores decay 5% per week. A font approved 6 months ago with
|
||||
10 approvals has less weight than one approved last week. The decay calculation
|
||||
happens at read time, not write time, so the file only grows on change.
|
||||
|
||||
**Schema migration:** If the file has no `version` field or `version: 0`, it's
|
||||
the legacy approved.json aggregate — `~/.claude/skills/gstack/bin/gstack-taste-update`
|
||||
will migrate it to schema v1 on the next write.
|
||||
|
||||
**Per-session approved.json files (legacy, still supported):**
|
||||
|
||||
```bash
|
||||
setopt +o nomatch 2>/dev/null || true
|
||||
@@ -793,14 +838,17 @@ _TASTE=$(find ~/.gstack/projects/$SLUG/designs/ -name "approved.json" -maxdepth
|
||||
```
|
||||
|
||||
If prior sessions exist, read each `approved.json` and extract patterns from the
|
||||
approved variants. Include a taste summary in the design brief:
|
||||
|
||||
"The user previously approved designs with these characteristics: [high contrast,
|
||||
generous whitespace, modern sans-serif typography, etc.]. Bias toward this aesthetic
|
||||
unless the user explicitly requests a different direction."
|
||||
approved variants. Merge these into the taste-profile.json-derived signal — if the
|
||||
profile already says "user prefers Geist font" (from aggregated history), the
|
||||
approved.json files add the specific recent approval context.
|
||||
|
||||
Limit to last 10 sessions. Try/catch JSON parse on each (skip corrupted files).
|
||||
|
||||
**Updating taste profile after a design-shotgun session:** When the user picks a
|
||||
variant, call `~/.claude/skills/gstack/bin/gstack-taste-update approved <variant-path>`. When they
|
||||
explicitly reject a variant, call `~/.claude/skills/gstack/bin/gstack-taste-update rejected <variant-path>`.
|
||||
The CLI handles schema migration from approved.json, decay, and conflict flagging.
|
||||
|
||||
## Step 3: Generate Variants
|
||||
|
||||
Set up the output directory:
|
||||
|
||||
@@ -116,7 +116,14 @@ Two rounds max of context gathering, then proceed with what you have and note as
|
||||
|
||||
## Step 2: Taste Memory
|
||||
|
||||
Read prior approved designs to bias generation toward the user's demonstrated taste:
|
||||
Read both the persistent taste profile (cross-session) AND the per-session approved
|
||||
designs to bias generation toward the user's demonstrated taste.
|
||||
|
||||
**Persistent taste profile (v1 schema at `~/.gstack/projects/$SLUG/taste-profile.json`):**
|
||||
|
||||
{{TASTE_PROFILE}}
|
||||
|
||||
**Per-session approved.json files (legacy, still supported):**
|
||||
|
||||
```bash
|
||||
setopt +o nomatch 2>/dev/null || true
|
||||
@@ -124,14 +131,17 @@ _TASTE=$(find ~/.gstack/projects/$SLUG/designs/ -name "approved.json" -maxdepth
|
||||
```
|
||||
|
||||
If prior sessions exist, read each `approved.json` and extract patterns from the
|
||||
approved variants. Include a taste summary in the design brief:
|
||||
|
||||
"The user previously approved designs with these characteristics: [high contrast,
|
||||
generous whitespace, modern sans-serif typography, etc.]. Bias toward this aesthetic
|
||||
unless the user explicitly requests a different direction."
|
||||
approved variants. Merge these into the taste-profile.json-derived signal — if the
|
||||
profile already says "user prefers Geist font" (from aggregated history), the
|
||||
approved.json files add the specific recent approval context.
|
||||
|
||||
Limit to last 10 sessions. Try/catch JSON parse on each (skip corrupted files).
|
||||
|
||||
**Updating taste profile after a design-shotgun session:** When the user picks a
|
||||
variant, call `{{BIN_DIR}}/gstack-taste-update approved <variant-path>`. When they
|
||||
explicitly reject a variant, call `{{BIN_DIR}}/gstack-taste-update rejected <variant-path>`.
|
||||
The CLI handles schema migration from approved.json, decay, and conflict flagging.
|
||||
|
||||
## Step 3: Generate Variants
|
||||
|
||||
Set up the output directory:
|
||||
|
||||
Reference in New Issue
Block a user