From b286989895954e34af7325b45f02cafdab3628d0 Mon Sep 17 00:00:00 2001 From: garrytan Date: Tue, 27 Oct 2026 23:42:07 +0000 Subject: [PATCH] refactor(learn): diet /learn onto the levels: tool flags replace the inline stats program, one mode table, prune rules kept MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Skill-Diet: learn Skill-Size: 5797→2586 bytes (SKILL.md.tmpl) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> --- learn/SKILL.md | 187 ++++++-------------------------------------- learn/SKILL.md.tmpl | 187 ++++++-------------------------------------- 2 files changed, 44 insertions(+), 330 deletions(-) diff --git a/learn/SKILL.md b/learn/SKILL.md index c5bd1df5a..7ed5359b8 100644 --- a/learn/SKILL.md +++ b/learn/SKILL.md @@ -1,7 +1,7 @@ --- name: learn preamble-tier: 2 -version: 1.0.0 +version: 1.1.0 description: Manage project learnings. triggers: - show learnings @@ -97,174 +97,31 @@ Replace `OUTCOME` and `USED_BROWSE` (yes/no) before running; substitute are "" unless outcome is error. If the command is missing (stale install), skip telemetry — it never blocks the workflow. -# Project Learnings Manager +# /learn: project learnings -You are a **Staff Engineer who maintains the team wiki**. Your job is to help the user -see what gstack has learned across sessions on this project, search for relevant -knowledge, and prune stale or contradictory entries. +**Hard gate:** manage learnings only. Make no code changes, even when a learning names a fix. -**HARD GATE:** Do NOT implement code changes. This skill manages learnings only. +`~/.claude/skills/gstack/bin/gstack-learnings-search` reads this project's learnings (latest entry per key+type); +`~/.claude/skills/gstack/bin/gstack-learnings-log` appends one. Both take `--help`. Pick the mode from the request: ---- - -## Detect command - -Parse the user's input to determine which command to run: - -- `/learn` (no arguments) → **Show recent** -- `/learn search ` → **Search** -- `/learn prune` → **Prune** -- `/learn export` → **Export** -- `/learn stats` → **Stats** -- `/learn add` → **Manual add** - ---- - -## Show recent (default) - -Show the most recent 20 learnings, grouped by type. - -```bash -SLUG=$(~/.claude/skills/gstack/bin/gstack-slug --get SLUG 2>/dev/null) -~/.claude/skills/gstack/bin/gstack-learnings-search --limit 20 2>/dev/null || echo "No learnings yet." -``` - -Present the output in a readable format. If no learnings exist, tell the user: -"No learnings recorded yet. As you use /review, /ship, /investigate, and other skills, -gstack will automatically capture patterns, pitfalls, and insights it discovers." - ---- - -## Search - -```bash -SLUG=$(~/.claude/skills/gstack/bin/gstack-slug --get SLUG 2>/dev/null) -~/.claude/skills/gstack/bin/gstack-learnings-search --query "USER_QUERY" --limit 20 2>/dev/null || echo "No matches." -``` - -Replace USER_QUERY with the user's search terms. Present results clearly. - ---- +| Request | Run | Then | +|---|---|---| +| show (default) | `gstack-learnings-search --limit 20` | group by type; if empty, say none are recorded yet and that /review, /ship, /investigate and other skills record them as they work | +| search | `gstack-learnings-search --query "" --limit 20` | show the matches | +| other projects | add `--cross-project` | only user-stated learnings from other projects appear | +| stats | `gstack-learnings-search --stats` | show the counts as a table | +| export | `gstack-learnings-search --limit 50` | a `## Project Learnings` section, `### s` per type, lines `- **key**: insight (confidence: N/10)`; show it, and write it into CLAUDE.md or a file only if the user asks | +| add | `gstack-learnings-log ''` | ask only for what is missing: type, kebab-case key, one-sentence insight, confidence 1-10, files; `"source":"user-stated"` | +| prune | `gstack-learnings-search --limit 100` | see Prune | ## Prune -Check learnings for staleness and contradictions. +Flag each learning that is: +- **STALE**: a path in its `files` no longer exists in the repo. +- **CONFLICT**: the same key appears under two types with opposing insights (within one type the latest entry already wins). -```bash -SLUG=$(~/.claude/skills/gstack/bin/gstack-slug --get SLUG 2>/dev/null) -~/.claude/skills/gstack/bin/gstack-learnings-search --limit 100 2>/dev/null -``` - -For each learning in the output: - -1. **File existence check:** If the learning has a `files` field, check whether those - files still exist in the repo using Glob. If any referenced files are deleted, flag: - "STALE: [key] references deleted file [path]" - -2. **Contradiction check:** Look for learnings with the same `key` but different or - opposite `insight` values. Flag: "CONFLICT: [key] has contradicting entries — - [insight A] vs [insight B]" - -Present each flagged entry via AskUserQuestion: -- A) Remove this learning -- B) Keep it -- C) Update it (I'll tell you what to change) - -For removals, read the learnings.jsonl file and remove the matching line, then write -back. For updates, append a new entry with the corrected insight (append-only, the -latest entry wins). - ---- - -## Export - -Export learnings as markdown suitable for adding to CLAUDE.md or project documentation. - -```bash -SLUG=$(~/.claude/skills/gstack/bin/gstack-slug --get SLUG 2>/dev/null) -~/.claude/skills/gstack/bin/gstack-learnings-search --limit 50 2>/dev/null -``` - -Format the output as a markdown section: - -```markdown -## Project Learnings - -### Patterns -- **[key]**: [insight] (confidence: N/10) - -### Pitfalls -- **[key]**: [insight] (confidence: N/10) - -### Preferences -- **[key]**: [insight] - -### Architecture -- **[key]**: [insight] (confidence: N/10) -``` - -Present the formatted output to the user. Ask if they want to append it to CLAUDE.md -or save it as a separate file. - ---- - -## Stats - -Show summary statistics about the project's learnings. - -```bash -SLUG=$(~/.claude/skills/gstack/bin/gstack-slug --get SLUG 2>/dev/null) -GSTACK_STATE_ROOT=$(~/.claude/skills/gstack/bin/gstack-paths --get GSTACK_STATE_ROOT); : "${GSTACK_STATE_ROOT:?gstack-paths failed; reinstall with ./setup or /gstack-upgrade}" -LEARN_FILE="$GSTACK_STATE_ROOT/projects/$SLUG/learnings.jsonl" -if [ -f "$LEARN_FILE" ]; then - TOTAL=$(wc -l < "$LEARN_FILE" | tr -d ' ') - echo "TOTAL: $TOTAL entries" - # Count by type (after dedup) - cat "$LEARN_FILE" | bun --no-env-file --config=/dev/null -e " - const lines = (await Bun.stdin.text()).trim().split('\n').filter(Boolean); - const seen = new Map(); - for (const line of lines) { - try { - const e = JSON.parse(line); - const dk = (e.key||'') + '|' + (e.type||''); - const existing = seen.get(dk); - if (!existing || new Date(e.ts) > new Date(existing.ts)) seen.set(dk, e); - } catch {} - } - const byType = {}; - const bySource = {}; - let totalConf = 0; - for (const e of seen.values()) { - byType[e.type] = (byType[e.type]||0) + 1; - bySource[e.source] = (bySource[e.source]||0) + 1; - totalConf += e.confidence || 0; - } - console.log('UNIQUE: ' + seen.size + ' (after dedup)'); - console.log('RAW_ENTRIES: ' + lines.length); - console.log('BY_TYPE: ' + JSON.stringify(byType)); - console.log('BY_SOURCE: ' + JSON.stringify(bySource)); - console.log('AVG_CONFIDENCE: ' + (totalConf / seen.size).toFixed(1)); - " 2>/dev/null -else - echo "NO_LEARNINGS" -fi -``` - -Present the stats in a readable table format. - ---- - -## Manual add - -The user wants to manually add a learning. Use AskUserQuestion to gather: -1. Type (pattern / pitfall / preference / architecture / tool) -2. A short key (2-5 words, kebab-case) -3. The insight (one sentence) -4. Confidence (1-10) -5. Related files (optional) - -Then log it: - -```bash -~/.claude/skills/gstack/bin/gstack-learnings-log '{"skill":"learn","type":"TYPE","key":"KEY","insight":"INSIGHT","confidence":N,"source":"user-stated","files":["FILE1"]}' -``` +For each flagged entry, unless the user already approved the action, ask: remove, keep, or update. +- Remove: delete its lines (that key and type) from `$GSTACK_STATE_ROOT/projects/$SLUG/learnings.jsonl`, where + `SLUG=$(~/.claude/skills/gstack/bin/gstack-slug --get SLUG)` and + `GSTACK_STATE_ROOT=$(~/.claude/skills/gstack/bin/gstack-paths --get GSTACK_STATE_ROOT)`. Leave every other line as it is. +- Update: log a corrected entry with the same key and type; the latest one wins. diff --git a/learn/SKILL.md.tmpl b/learn/SKILL.md.tmpl index e0da50776..ed309fe29 100644 --- a/learn/SKILL.md.tmpl +++ b/learn/SKILL.md.tmpl @@ -1,7 +1,7 @@ --- name: learn preamble-tier: 2 -version: 1.0.0 +version: 1.1.0 description: | Manage project learnings. Review, search, prune, and export what gstack has learned across sessions. Use when asked to "what have we learned", @@ -24,174 +24,31 @@ allowed-tools: {{PREAMBLE}} -# Project Learnings Manager +# /learn: project learnings -You are a **Staff Engineer who maintains the team wiki**. Your job is to help the user -see what gstack has learned across sessions on this project, search for relevant -knowledge, and prune stale or contradictory entries. +**Hard gate:** manage learnings only. Make no code changes, even when a learning names a fix. -**HARD GATE:** Do NOT implement code changes. This skill manages learnings only. +`~/.claude/skills/gstack/bin/gstack-learnings-search` reads this project's learnings (latest entry per key+type); +`~/.claude/skills/gstack/bin/gstack-learnings-log` appends one. Both take `--help`. Pick the mode from the request: ---- - -## Detect command - -Parse the user's input to determine which command to run: - -- `/learn` (no arguments) → **Show recent** -- `/learn search ` → **Search** -- `/learn prune` → **Prune** -- `/learn export` → **Export** -- `/learn stats` → **Stats** -- `/learn add` → **Manual add** - ---- - -## Show recent (default) - -Show the most recent 20 learnings, grouped by type. - -```bash -SLUG=$(~/.claude/skills/gstack/bin/gstack-slug --get SLUG 2>/dev/null) -~/.claude/skills/gstack/bin/gstack-learnings-search --limit 20 2>/dev/null || echo "No learnings yet." -``` - -Present the output in a readable format. If no learnings exist, tell the user: -"No learnings recorded yet. As you use /review, /ship, /investigate, and other skills, -gstack will automatically capture patterns, pitfalls, and insights it discovers." - ---- - -## Search - -```bash -SLUG=$(~/.claude/skills/gstack/bin/gstack-slug --get SLUG 2>/dev/null) -~/.claude/skills/gstack/bin/gstack-learnings-search --query "USER_QUERY" --limit 20 2>/dev/null || echo "No matches." -``` - -Replace USER_QUERY with the user's search terms. Present results clearly. - ---- +| Request | Run | Then | +|---|---|---| +| show (default) | `gstack-learnings-search --limit 20` | group by type; if empty, say none are recorded yet and that /review, /ship, /investigate and other skills record them as they work | +| search | `gstack-learnings-search --query "" --limit 20` | show the matches | +| other projects | add `--cross-project` | only user-stated learnings from other projects appear | +| stats | `gstack-learnings-search --stats` | show the counts as a table | +| export | `gstack-learnings-search --limit 50` | a `## Project Learnings` section, `### s` per type, lines `- **key**: insight (confidence: N/10)`; show it, and write it into CLAUDE.md or a file only if the user asks | +| add | `gstack-learnings-log ''` | ask only for what is missing: type, kebab-case key, one-sentence insight, confidence 1-10, files; `"source":"user-stated"` | +| prune | `gstack-learnings-search --limit 100` | see Prune | ## Prune -Check learnings for staleness and contradictions. +Flag each learning that is: +- **STALE**: a path in its `files` no longer exists in the repo. +- **CONFLICT**: the same key appears under two types with opposing insights (within one type the latest entry already wins). -```bash -SLUG=$(~/.claude/skills/gstack/bin/gstack-slug --get SLUG 2>/dev/null) -~/.claude/skills/gstack/bin/gstack-learnings-search --limit 100 2>/dev/null -``` - -For each learning in the output: - -1. **File existence check:** If the learning has a `files` field, check whether those - files still exist in the repo using Glob. If any referenced files are deleted, flag: - "STALE: [key] references deleted file [path]" - -2. **Contradiction check:** Look for learnings with the same `key` but different or - opposite `insight` values. Flag: "CONFLICT: [key] has contradicting entries — - [insight A] vs [insight B]" - -Present each flagged entry via AskUserQuestion: -- A) Remove this learning -- B) Keep it -- C) Update it (I'll tell you what to change) - -For removals, read the learnings.jsonl file and remove the matching line, then write -back. For updates, append a new entry with the corrected insight (append-only, the -latest entry wins). - ---- - -## Export - -Export learnings as markdown suitable for adding to CLAUDE.md or project documentation. - -```bash -SLUG=$(~/.claude/skills/gstack/bin/gstack-slug --get SLUG 2>/dev/null) -~/.claude/skills/gstack/bin/gstack-learnings-search --limit 50 2>/dev/null -``` - -Format the output as a markdown section: - -```markdown -## Project Learnings - -### Patterns -- **[key]**: [insight] (confidence: N/10) - -### Pitfalls -- **[key]**: [insight] (confidence: N/10) - -### Preferences -- **[key]**: [insight] - -### Architecture -- **[key]**: [insight] (confidence: N/10) -``` - -Present the formatted output to the user. Ask if they want to append it to CLAUDE.md -or save it as a separate file. - ---- - -## Stats - -Show summary statistics about the project's learnings. - -```bash -SLUG=$(~/.claude/skills/gstack/bin/gstack-slug --get SLUG 2>/dev/null) -GSTACK_STATE_ROOT=$(~/.claude/skills/gstack/bin/gstack-paths --get GSTACK_STATE_ROOT); : "${GSTACK_STATE_ROOT:?gstack-paths failed; reinstall with ./setup or /gstack-upgrade}" -LEARN_FILE="$GSTACK_STATE_ROOT/projects/$SLUG/learnings.jsonl" -if [ -f "$LEARN_FILE" ]; then - TOTAL=$(wc -l < "$LEARN_FILE" | tr -d ' ') - echo "TOTAL: $TOTAL entries" - # Count by type (after dedup) - cat "$LEARN_FILE" | bun --no-env-file --config=/dev/null -e " - const lines = (await Bun.stdin.text()).trim().split('\n').filter(Boolean); - const seen = new Map(); - for (const line of lines) { - try { - const e = JSON.parse(line); - const dk = (e.key||'') + '|' + (e.type||''); - const existing = seen.get(dk); - if (!existing || new Date(e.ts) > new Date(existing.ts)) seen.set(dk, e); - } catch {} - } - const byType = {}; - const bySource = {}; - let totalConf = 0; - for (const e of seen.values()) { - byType[e.type] = (byType[e.type]||0) + 1; - bySource[e.source] = (bySource[e.source]||0) + 1; - totalConf += e.confidence || 0; - } - console.log('UNIQUE: ' + seen.size + ' (after dedup)'); - console.log('RAW_ENTRIES: ' + lines.length); - console.log('BY_TYPE: ' + JSON.stringify(byType)); - console.log('BY_SOURCE: ' + JSON.stringify(bySource)); - console.log('AVG_CONFIDENCE: ' + (totalConf / seen.size).toFixed(1)); - " 2>/dev/null -else - echo "NO_LEARNINGS" -fi -``` - -Present the stats in a readable table format. - ---- - -## Manual add - -The user wants to manually add a learning. Use AskUserQuestion to gather: -1. Type (pattern / pitfall / preference / architecture / tool) -2. A short key (2-5 words, kebab-case) -3. The insight (one sentence) -4. Confidence (1-10) -5. Related files (optional) - -Then log it: - -```bash -~/.claude/skills/gstack/bin/gstack-learnings-log '{"skill":"learn","type":"TYPE","key":"KEY","insight":"INSIGHT","confidence":N,"source":"user-stated","files":["FILE1"]}' -``` +For each flagged entry, unless the user already approved the action, ask: remove, keep, or update. +- Remove: delete its lines (that key and type) from `$GSTACK_STATE_ROOT/projects/$SLUG/learnings.jsonl`, where + `SLUG=$(~/.claude/skills/gstack/bin/gstack-slug --get SLUG)` and + `GSTACK_STATE_ROOT=$(~/.claude/skills/gstack/bin/gstack-paths --get GSTACK_STATE_ROOT)`. Leave every other line as it is. +- Update: log a corrected entry with the same key and type; the latest one wins.