diff --git a/CHANGELOG.md b/CHANGELOG.md index 8182c5f25..d31128370 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,22 @@ # Changelog +## [0.11.11.0] - 2026-03-23 — /meditate: Repo Consciousness + +### Added + +- **`/meditate` — proactive repo consciousness.** Inspired by Vipassana body-scanning meditation: a compiled binary scans your repo structure, git history, and past AI conversations (Claude Code, Codex CLI, Gemini CLI) to produce `.gstack/context.md`. Every other skill reads this file and becomes smarter — `/qa` knows which flows to hammer, `/review` focuses on high-risk files, `/cso` already knows the attack surface. One command, compound awareness. +- **Daily background scanning.** The preamble checks context freshness on every skill invocation. When stale (>24h), it nudges you to run `/meditate`. Context builds up over time — each meditation compounds on the last. +- **Multi-AI conversation mining.** Scans sessions from all three AI CLIs — Claude Code (`~/.claude/projects/`), Codex (`~/.codex/sessions/`), and Gemini (`~/.gemini/tmp/`). Extracts recurring file references, error patterns, workflow habits, and skill usage across tools. Privacy-first: only patterns appearing in 3+ sessions are included, never raw prose. +- **Six-section context file.** Architecture Map (mental model, not file list), Hotspots (where energy flows), Conventions (what the codebase "believes"), User Taste (what you care about), Recurring Problems (patterns to be aware of), Watch These Next (skill-specific foresight). Capped at 1024 lines. +- **Template-based `--background` mode.** Scanner binary produces a basic `context.md` directly from JSON data without LLM tokens. Running `/meditate` explicitly adds Claude's deeper synthesis — richer architecture analysis, nuanced convention detection, and skill-specific foresight. +- **10 unit tests** covering scanner functions, slug derivation, language detection, template synthesis, and CLI integration. + +### For contributors + +- `bin/gstack-meditate.ts` compiled via `bun build --compile` (same pattern as browse binary) +- Conversation mining reuses `extractCwdFromJsonl()` pattern from `gstack-global-discover.ts` +- Touchfile entry: `meditate-scanner` triggers on `bin/gstack-meditate.ts`, `meditate/**`, `test/meditate-scanner.test.ts` + ## [0.11.10.0] - 2026-03-23 — CI Evals on Ubicloud ### Added diff --git a/SKILL.md b/SKILL.md index af9ef7b06..8d6df8c59 100644 --- a/SKILL.md +++ b/SKILL.md @@ -52,6 +52,14 @@ mkdir -p ~/.gstack/analytics echo '{"skill":"gstack","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true # zsh-compatible: use find instead of glob to avoid NOMATCH error for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do [ -f "$_PF" ] && ~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown --session-id "$_SESSION_ID" 2>/dev/null || true; break; done +# Meditation freshness check + daily background scan +if [ -f ".gstack/context.md" ]; then + _MED_MOD=$(stat -f %m .gstack/context.md 2>/dev/null || stat -c %Y .gstack/context.md 2>/dev/null || echo 0) + _MED_AGE=$(( $(date +%s) - _MED_MOD )) + [ "$_MED_AGE" -gt 86400 ] && echo "MEDITATION_STALE" || echo "MEDITATION_FRESH" +else + echo "MEDITATION_MISSING" +fi ``` If `PROACTIVE` is `"false"`, do not proactively suggest gstack skills — only invoke @@ -153,6 +161,19 @@ AI-assisted coding makes the marginal cost of completeness near-zero. When you p Never let a noticed issue silently pass. The whole point is proactive communication. +## Meditation Context + +The preamble prints `MEDITATION_FRESH`, `MEDITATION_STALE`, or `MEDITATION_MISSING`. + +If `MEDITATION_FRESH`: Read `.gstack/context.md` silently and use its contents to +inform your work. Do not mention meditation to the user — just be smarter because of it. +The context file contains: Architecture Map, Hotspots, Conventions, User Taste, +Recurring Problems, and Watch These Next — use whichever sections are relevant. + +If `MEDITATION_STALE` or `MEDITATION_MISSING`: Tell the user: +"[gstack] Repo consciousness is stale. Run /meditate for better results." +Continue normally — this is informational, not blocking. + ## Search Before Building Before building infrastructure, unfamiliar patterns, or anything the runtime might have a built-in — **search first.** Read `~/.claude/skills/gstack/ETHOS.md` for the full philosophy. diff --git a/VERSION b/VERSION index 6bfbae754..b8993a2ad 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.11.10.0 +0.11.11.0 diff --git a/autoplan/SKILL.md b/autoplan/SKILL.md index ec75c5507..f7124101d 100644 --- a/autoplan/SKILL.md +++ b/autoplan/SKILL.md @@ -53,6 +53,14 @@ mkdir -p ~/.gstack/analytics echo '{"skill":"autoplan","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true # zsh-compatible: use find instead of glob to avoid NOMATCH error for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do [ -f "$_PF" ] && ~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown --session-id "$_SESSION_ID" 2>/dev/null || true; break; done +# Meditation freshness check + daily background scan +if [ -f ".gstack/context.md" ]; then + _MED_MOD=$(stat -f %m .gstack/context.md 2>/dev/null || stat -c %Y .gstack/context.md 2>/dev/null || echo 0) + _MED_AGE=$(( $(date +%s) - _MED_MOD )) + [ "$_MED_AGE" -gt 86400 ] && echo "MEDITATION_STALE" || echo "MEDITATION_FRESH" +else + echo "MEDITATION_MISSING" +fi ``` If `PROACTIVE` is `"false"`, do not proactively suggest gstack skills — only invoke @@ -154,6 +162,19 @@ AI-assisted coding makes the marginal cost of completeness near-zero. When you p Never let a noticed issue silently pass. The whole point is proactive communication. +## Meditation Context + +The preamble prints `MEDITATION_FRESH`, `MEDITATION_STALE`, or `MEDITATION_MISSING`. + +If `MEDITATION_FRESH`: Read `.gstack/context.md` silently and use its contents to +inform your work. Do not mention meditation to the user — just be smarter because of it. +The context file contains: Architecture Map, Hotspots, Conventions, User Taste, +Recurring Problems, and Watch These Next — use whichever sections are relevant. + +If `MEDITATION_STALE` or `MEDITATION_MISSING`: Tell the user: +"[gstack] Repo consciousness is stale. Run /meditate for better results." +Continue normally — this is informational, not blocking. + ## Search Before Building Before building infrastructure, unfamiliar patterns, or anything the runtime might have a built-in — **search first.** Read `~/.claude/skills/gstack/ETHOS.md` for the full philosophy. diff --git a/benchmark/SKILL.md b/benchmark/SKILL.md index 7a3e7432c..1533bde62 100644 --- a/benchmark/SKILL.md +++ b/benchmark/SKILL.md @@ -46,6 +46,14 @@ mkdir -p ~/.gstack/analytics echo '{"skill":"benchmark","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true # zsh-compatible: use find instead of glob to avoid NOMATCH error for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do [ -f "$_PF" ] && ~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown --session-id "$_SESSION_ID" 2>/dev/null || true; break; done +# Meditation freshness check + daily background scan +if [ -f ".gstack/context.md" ]; then + _MED_MOD=$(stat -f %m .gstack/context.md 2>/dev/null || stat -c %Y .gstack/context.md 2>/dev/null || echo 0) + _MED_AGE=$(( $(date +%s) - _MED_MOD )) + [ "$_MED_AGE" -gt 86400 ] && echo "MEDITATION_STALE" || echo "MEDITATION_FRESH" +else + echo "MEDITATION_MISSING" +fi ``` If `PROACTIVE` is `"false"`, do not proactively suggest gstack skills — only invoke @@ -147,6 +155,19 @@ AI-assisted coding makes the marginal cost of completeness near-zero. When you p Never let a noticed issue silently pass. The whole point is proactive communication. +## Meditation Context + +The preamble prints `MEDITATION_FRESH`, `MEDITATION_STALE`, or `MEDITATION_MISSING`. + +If `MEDITATION_FRESH`: Read `.gstack/context.md` silently and use its contents to +inform your work. Do not mention meditation to the user — just be smarter because of it. +The context file contains: Architecture Map, Hotspots, Conventions, User Taste, +Recurring Problems, and Watch These Next — use whichever sections are relevant. + +If `MEDITATION_STALE` or `MEDITATION_MISSING`: Tell the user: +"[gstack] Repo consciousness is stale. Run /meditate for better results." +Continue normally — this is informational, not blocking. + ## Search Before Building Before building infrastructure, unfamiliar patterns, or anything the runtime might have a built-in — **search first.** Read `~/.claude/skills/gstack/ETHOS.md` for the full philosophy. diff --git a/bin/gstack-meditate.ts b/bin/gstack-meditate.ts new file mode 100644 index 000000000..89ff39107 --- /dev/null +++ b/bin/gstack-meditate.ts @@ -0,0 +1,784 @@ +#!/usr/bin/env bun +/** + * gstack-meditate — Proactive repo consciousness scanner. + * + * Scans repo structure, git history, and past AI conversations (Claude Code, + * Codex CLI, Gemini CLI) to produce a raw JSON snapshot. Optionally generates + * a template-based .gstack/context.md (--background mode). + * + * Usage: + * gstack-meditate --repo [--output ] [--background] + * gstack-meditate --help + */ + +import { + existsSync, readdirSync, statSync, readFileSync, writeFileSync, + openSync, readSync, closeSync, mkdirSync, renameSync, unlinkSync, +} from "fs"; +import { join, basename, relative, resolve } from "path"; +import { execSync, spawnSync } from "child_process"; +import { homedir, tmpdir } from "os"; + +// ── Types ────────────────────────────────────────────────────────────────── + +interface RepoSnapshot { + version: number; + timestamp: string; + duration_ms: number; + repo: { + slug: string; + remote: string; + languages: string[]; + framework: string; + structure: Record; + file_count: number; + test_coverage_map: Record; + }; + activity: { + commits_30d: number; + contributors: string[]; + hotspots: string[]; + cold_spots: string[]; + todos: { file: string; line: number; text: string }[]; + }; + conversations: { + sessions_analyzed: number; + sessions_skipped: number; + by_tool: { claude_code: number; codex: number; gemini: number }; + most_referenced_files: string[]; + recurring_errors: string[]; + recurring_topics: string[]; + workflow_patterns: string[]; + }; + docs: { + claude_md: string; + todos_md: string; + readme_md: string; + }; + partial: boolean; +} + +interface ConversationSession { + tool: "claude_code" | "codex" | "gemini"; + filePath: string; + cwd: string; + mtime: Date; +} + +interface ExtractedPatterns { + referencedFiles: Map; + errors: Map; + topics: Map; + workflows: Map; + sessionsAnalyzed: number; + sessionsSkipped: number; + byTool: { claude_code: number; codex: number; gemini: number }; +} + +// ── CLI ──────────────────────────────────────────────────────────────────── + +function printUsage(): void { + console.error(`Usage: gstack-meditate --repo [--output ] [--background] + + --repo Repository root to scan + --output Output path for JSON snapshot (default: auto) + --background Also produce template-based .gstack/context.md (no LLM) + --help Show this help`); +} + +function parseArgs(): { repo: string; output: string; background: boolean } { + const args = process.argv.slice(2); + let repo = ""; + let output = ""; + let background = false; + + for (let i = 0; i < args.length; i++) { + if (args[i] === "--help" || args[i] === "-h") { + printUsage(); + process.exit(0); + } else if (args[i] === "--repo" && args[i + 1]) { + repo = args[++i]; + } else if (args[i] === "--output" && args[i + 1]) { + output = args[++i]; + } else if (args[i] === "--background") { + background = true; + } + } + + if (!repo) { + repo = process.cwd(); + } + + return { repo: resolve(repo), output, background }; +} + +// ── Slug derivation ──────────────────────────────────────────────────────── + +export function deriveSlug(repoPath: string): string { + try { + const remote = execSync("git remote get-url origin", { + cwd: repoPath, stdio: ["pipe", "pipe", "pipe"], timeout: 5000, + }).toString().trim(); + // Extract owner/repo from URL + const match = remote.match(/[:/]([^/]+\/[^/]+?)(?:\.git)?$/); + if (match) { + return match[1].replace("/", "-").replace(/[^a-zA-Z0-9._-]/g, ""); + } + } catch {} + return basename(repoPath).replace(/[^a-zA-Z0-9._-]/g, ""); +} + +// ── Repo scanning ────────────────────────────────────────────────────────── + +export function scanRepoStructure(repoPath: string, maxDepth = 4): Record { + const structure: Record = {}; + const ignoreSet = new Set([ + "node_modules", ".git", "dist", "build", ".next", "__pycache__", + "vendor", ".bundle", "target", "coverage", ".gstack", + ]); + + function walk(dir: string, depth: number): void { + if (depth > maxDepth) return; + try { + const entries = readdirSync(dir, { withFileTypes: true }); + const rel = relative(repoPath, dir) || "."; + const children: string[] = []; + for (const e of entries) { + if (e.name.startsWith(".") && e.name !== ".github") continue; + if (ignoreSet.has(e.name)) continue; + children.push(e.isDirectory() ? `${e.name}/` : e.name); + if (e.isDirectory()) walk(join(dir, e.name), depth + 1); + } + if (children.length > 0) structure[rel] = children; + } catch {} + } + + walk(repoPath, 0); + return structure; +} + +export function detectLanguages(repoPath: string): { languages: string[]; framework: string } { + const languages: string[] = []; + const detectors: [string, string][] = [ + ["package.json", "typescript"], + ["tsconfig.json", "typescript"], + ["Gemfile", "ruby"], + ["pyproject.toml", "python"], + ["requirements.txt", "python"], + ["go.mod", "go"], + ["Cargo.toml", "rust"], + ["pom.xml", "java"], + ["build.gradle", "java"], + ["composer.json", "php"], + ["mix.exs", "elixir"], + ["Makefile", "c/c++"], + ]; + + for (const [file, lang] of detectors) { + if (existsSync(join(repoPath, file)) && !languages.includes(lang)) { + languages.push(lang); + } + } + + // Check for bash/shell scripts + try { + const result = spawnSync("find", [repoPath, "-maxdepth", "3", "-name", "*.sh", "-type", "f"], { + timeout: 3000, stdio: ["pipe", "pipe", "pipe"], + }); + if (result.stdout?.toString().trim()) languages.push("bash"); + } catch {} + + let framework = "unknown"; + if (existsSync(join(repoPath, "bun.lock")) || existsSync(join(repoPath, "bunfig.toml"))) { + framework = "bun"; + } else if (existsSync(join(repoPath, "next.config.js")) || existsSync(join(repoPath, "next.config.ts"))) { + framework = "nextjs"; + } else if (existsSync(join(repoPath, "vite.config.ts")) || existsSync(join(repoPath, "vite.config.js"))) { + framework = "vite"; + } else if (existsSync(join(repoPath, "Gemfile"))) { + framework = "rails"; + } else if (existsSync(join(repoPath, "package.json"))) { + framework = "node"; + } + + return { languages: languages.length ? languages : ["unknown"], framework }; +} + +export function scanGitActivity(repoPath: string): { + commits_30d: number; contributors: string[]; hotspots: string[]; cold_spots: string[]; +} { + const result = { commits_30d: 0, contributors: [] as string[], hotspots: [] as string[], cold_spots: [] as string[] }; + + try { + // Commit count + const countOut = execSync('git log --since="30 days" --oneline 2>/dev/null | wc -l', { + cwd: repoPath, timeout: 10000, stdio: ["pipe", "pipe", "pipe"], + }).toString().trim(); + result.commits_30d = parseInt(countOut, 10) || 0; + + // Contributors + const authorsOut = execSync('git log --since="30 days" --format="%an" 2>/dev/null | sort -u', { + cwd: repoPath, timeout: 10000, stdio: ["pipe", "pipe", "pipe"], + }).toString().trim(); + result.contributors = authorsOut.split("\n").filter(Boolean); + + // Hotspots (most-changed files) + const hotOut = execSync('git log --since="30 days" --name-only --format="" 2>/dev/null | sort | uniq -c | sort -rn | head -15', { + cwd: repoPath, timeout: 10000, stdio: ["pipe", "pipe", "pipe"], + }).toString().trim(); + result.hotspots = hotOut.split("\n").filter(Boolean).map(l => l.trim().replace(/^\d+\s+/, "")).filter(Boolean); + + // Cold spots (files not touched in 6+ months but in the tree) + const coldOut = execSync('git log --since="180 days" --name-only --format="" 2>/dev/null | sort -u', { + cwd: repoPath, timeout: 10000, stdio: ["pipe", "pipe", "pipe"], + }).toString().trim(); + const recentFiles = new Set(coldOut.split("\n").filter(Boolean)); + try { + const allFiles = execSync('git ls-files 2>/dev/null | head -500', { + cwd: repoPath, timeout: 5000, stdio: ["pipe", "pipe", "pipe"], + }).toString().trim().split("\n").filter(Boolean); + result.cold_spots = allFiles.filter(f => !recentFiles.has(f)).slice(0, 10); + } catch {} + } catch {} + + return result; +} + +export function scanTodos(repoPath: string, limit = 100): { file: string; line: number; text: string }[] { + const todos: { file: string; line: number; text: string }[] = []; + try { + const out = execSync( + 'grep -rn "TODO\\|FIXME\\|HACK\\|XXX" --include="*.ts" --include="*.js" --include="*.py" --include="*.rb" --include="*.go" --include="*.rs" --include="*.md" . 2>/dev/null | head -' + limit, + { cwd: repoPath, timeout: 10000, stdio: ["pipe", "pipe", "pipe"] }, + ).toString().trim(); + for (const line of out.split("\n").filter(Boolean)) { + const match = line.match(/^\.\/(.+?):(\d+):(.+)$/); + if (match) { + todos.push({ file: match[1], line: parseInt(match[2], 10), text: match[3].trim().slice(0, 120) }); + } + } + } catch {} + return todos; +} + +export function mapTestCoverage(repoPath: string): Record { + const map: Record = {}; + const testPatterns = [ + (f: string) => f.replace(/\.ts$/, ".test.ts"), + (f: string) => f.replace(/^src\//, "test/").replace(/\.ts$/, ".test.ts"), + (f: string) => f.replace(/^src\//, "__tests__/").replace(/\.ts$/, ".test.ts"), + (f: string) => f.replace(/\.py$/, "_test.py"), + (f: string) => `test_${basename(f)}`, + ]; + + try { + const files = execSync('git ls-files "*.ts" "*.py" "*.rb" "*.js" 2>/dev/null | head -200', { + cwd: repoPath, timeout: 5000, stdio: ["pipe", "pipe", "pipe"], + }).toString().trim().split("\n").filter(Boolean); + + for (const file of files) { + if (file.includes(".test.") || file.includes("_test.") || file.startsWith("test/")) continue; + for (const pattern of testPatterns) { + const testFile = pattern(file); + if (existsSync(join(repoPath, testFile))) { + map[file] = testFile; + break; + } + } + } + } catch {} + return map; +} + +export function extractDocs(repoPath: string): { claude_md: string; todos_md: string; readme_md: string } { + const readFirst = (name: string, maxBytes = 2048): string => { + const p = join(repoPath, name); + if (!existsSync(p)) return ""; + try { + const fd = openSync(p, "r"); + const buf = Buffer.alloc(maxBytes); + const n = readSync(fd, buf, 0, maxBytes, 0); + closeSync(fd); + return buf.toString("utf-8", 0, n); + } catch { return ""; } + }; + return { + claude_md: readFirst("CLAUDE.md"), + todos_md: readFirst("TODOS.md"), + readme_md: readFirst("README.md"), + }; +} + +// ── Conversation mining ──────────────────────────────────────────────────── + +function extractCwdFromJsonl(filePath: string): string | null { + try { + const fd = openSync(filePath, "r"); + const buf = Buffer.alloc(8192); + const bytesRead = readSync(fd, buf, 0, 8192, 0); + closeSync(fd); + const text = buf.toString("utf-8", 0, bytesRead); + const lines = text.split("\n").slice(0, 15); + for (const line of lines) { + if (!line.trim()) continue; + try { + const obj = JSON.parse(line); + if (obj.cwd) return obj.cwd; + } catch { continue; } + } + } catch {} + return null; +} + +function extractCwdFromCodexJsonl(filePath: string): string | null { + try { + const fd = openSync(filePath, "r"); + const buf = Buffer.alloc(4096); + const bytesRead = readSync(fd, buf, 0, 4096, 0); + closeSync(fd); + const firstLine = buf.toString("utf-8", 0, bytesRead).split("\n")[0]; + if (!firstLine) return null; + const meta = JSON.parse(firstLine); + if (meta.type === "session_meta" && meta.payload?.cwd) return meta.payload.cwd; + } catch {} + return null; +} + +export function discoverClaudeSessions(repoPath: string, since: Date): ConversationSession[] { + const projectsDir = join(homedir(), ".claude", "projects"); + if (!existsSync(projectsDir)) return []; + + const sessions: ConversationSession[] = []; + const resolvedRepo = resolve(repoPath); + + try { + for (const dirEntry of readdirSync(projectsDir, { withFileTypes: true })) { + if (!dirEntry.isDirectory()) continue; + const dirPath = join(projectsDir, dirEntry.name); + + // Check mtime — skip dirs not modified in 30 days + try { + if (statSync(dirPath).mtime < since) continue; + } catch { continue; } + + // Try to match by decoded directory name + const decoded = dirEntry.name.replace(/-/g, "/"); + const isMatch = decoded === resolvedRepo || decoded.endsWith(resolvedRepo) || + resolvedRepo.includes(dirEntry.name.replace(/-/g, "/").replace(/^\//, "")); + + if (!isMatch) { + // Fallback: check JSONL for cwd + const files = readdirSync(dirPath).filter(f => f.endsWith(".jsonl")); + if (files.length === 0) continue; + const cwd = extractCwdFromJsonl(join(dirPath, files[0])); + if (!cwd || resolve(cwd) !== resolvedRepo) continue; + } + + // Found matching project dir — list JSONL files + const jsonlFiles = readdirSync(dirPath) + .filter(f => f.endsWith(".jsonl")) + .map(f => ({ name: f, mtime: statSync(join(dirPath, f)).mtime })) + .filter(f => f.mtime >= since) + .sort((a, b) => b.mtime.getTime() - a.mtime.getTime()) + .slice(0, 20); + + for (const f of jsonlFiles) { + sessions.push({ + tool: "claude_code", + filePath: join(dirPath, f.name), + cwd: resolvedRepo, + mtime: f.mtime, + }); + } + } + } catch {} + return sessions; +} + +export function discoverCodexSessions(repoPath: string, since: Date): ConversationSession[] { + const sessionsDir = join(homedir(), ".codex", "sessions"); + if (!existsSync(sessionsDir)) return []; + + const sessions: ConversationSession[] = []; + const resolvedRepo = resolve(repoPath); + + try { + const years = readdirSync(sessionsDir); + for (const year of years) { + const yearPath = join(sessionsDir, year); + if (!statSync(yearPath).isDirectory()) continue; + for (const month of readdirSync(yearPath)) { + const monthPath = join(yearPath, month); + if (!statSync(monthPath).isDirectory()) continue; + for (const day of readdirSync(monthPath)) { + const dayPath = join(monthPath, day); + if (!statSync(dayPath).isDirectory()) continue; + const files = readdirSync(dayPath).filter(f => f.startsWith("rollout-") && f.endsWith(".jsonl")); + for (const file of files) { + const filePath = join(dayPath, file); + try { + const stat = statSync(filePath); + if (stat.mtime < since) continue; + const cwd = extractCwdFromCodexJsonl(filePath); + if (cwd && resolve(cwd) === resolvedRepo) { + sessions.push({ tool: "codex", filePath, cwd: resolvedRepo, mtime: stat.mtime }); + } + } catch { continue; } + } + } + } + } + } catch {} + + return sessions.sort((a, b) => b.mtime.getTime() - a.mtime.getTime()).slice(0, 20); +} + +export function discoverGeminiSessions(repoPath: string, since: Date): ConversationSession[] { + const tmpDir = join(homedir(), ".gemini", "tmp"); + if (!existsSync(tmpDir)) return []; + + const sessions: ConversationSession[] = []; + const resolvedRepo = resolve(repoPath); + + // Load projects.json + let projectsMap: Record = {}; + const projectsPath = join(homedir(), ".gemini", "projects.json"); + if (existsSync(projectsPath)) { + try { + const data = JSON.parse(readFileSync(projectsPath, { encoding: "utf-8" })); + const projects = data.projects || {}; + for (const [path, name] of Object.entries(projects)) { + projectsMap[name as string] = path; + } + } catch {} + } + + try { + for (const projectName of readdirSync(tmpDir)) { + const chatsDir = join(tmpDir, projectName, "chats"); + if (!existsSync(chatsDir)) continue; + + let cwd = projectsMap[projectName] || null; + if (!cwd) { + const rootFile = join(tmpDir, projectName, ".project_root"); + if (existsSync(rootFile)) { + try { cwd = readFileSync(rootFile, { encoding: "utf-8" }).trim(); } catch {} + } + } + if (!cwd || resolve(cwd) !== resolvedRepo) continue; + + const files = readdirSync(chatsDir) + .filter(f => f.startsWith("session-") && f.endsWith(".json")) + .map(f => ({ name: f, mtime: statSync(join(chatsDir, f)).mtime })) + .filter(f => f.mtime >= since) + .sort((a, b) => b.mtime.getTime() - a.mtime.getTime()) + .slice(0, 20); + + for (const f of files) { + sessions.push({ tool: "gemini", filePath: join(chatsDir, f.name), cwd: resolvedRepo, mtime: f.mtime }); + } + } + } catch {} + + return sessions; +} + +export function extractPatterns(sessions: ConversationSession[]): ExtractedPatterns { + const patterns: ExtractedPatterns = { + referencedFiles: new Map(), + errors: new Map(), + topics: new Map(), + workflows: new Map(), + sessionsAnalyzed: 0, + sessionsSkipped: 0, + byTool: { claude_code: 0, codex: 0, gemini: 0 }, + }; + + for (const session of sessions) { + try { + const stat = statSync(session.filePath); + const maxRead = stat.size > 50 * 1024 * 1024 ? 100 * 1024 : Math.min(stat.size, 5 * 1024 * 1024); + // Read only maxRead bytes to avoid loading huge files into memory + const fd = openSync(session.filePath, "r"); + const buf = Buffer.alloc(maxRead); + const bytesRead = readSync(fd, buf, 0, maxRead, 0); + closeSync(fd); + const content = buf.toString("utf-8", 0, bytesRead); + + // Extract file references (paths that look like source files) + const fileRefs = content.match(/(?:["']|\/)([\w.-]+\/[\w.-]+\.(?:ts|js|py|rb|go|rs|md|yaml|json))/g) || []; + for (const ref of fileRefs) { + const clean = ref.replace(/^["'/]/, ""); + patterns.referencedFiles.set(clean, (patterns.referencedFiles.get(clean) || 0) + 1); + } + + // Extract error patterns + const errorPatterns = content.match(/(?:Error|error|FAIL|FAILED|Exception|TypeError|SyntaxError)[:\s].{10,80}/g) || []; + for (const err of errorPatterns) { + const normalized = err.trim().slice(0, 80); + patterns.errors.set(normalized, (patterns.errors.get(normalized) || 0) + 1); + } + + // Extract workflow patterns (skill invocations) + const skillRefs = content.match(/\/(?:qa|review|ship|cso|investigate|meditate|retro|office-hours|plan-eng-review|plan-ceo-review|design-review)\b/g) || []; + for (const skill of skillRefs) { + patterns.workflows.set(skill, (patterns.workflows.get(skill) || 0) + 1); + } + + // Extract topic clusters by directory + const dirRefs = content.match(/(?:["']|\/)([\w.-]+)\//g) || []; + for (const dir of dirRefs) { + const clean = dir.replace(/^["'/]/, "").replace(/\/$/, ""); + if (clean.length > 1 && clean.length < 30 && !clean.startsWith(".")) { + patterns.topics.set(clean, (patterns.topics.get(clean) || 0) + 1); + } + } + + patterns.sessionsAnalyzed++; + patterns.byTool[session.tool]++; + } catch { + patterns.sessionsSkipped++; + } + } + + return patterns; +} + +function topEntries(map: Map, threshold: number, limit: number): string[] { + return [...map.entries()] + .filter(([, count]) => count >= threshold) + .sort(([, a], [, b]) => b - a) + .slice(0, limit) + .map(([key]) => key); +} + +// ── Template synthesis ───────────────────────────────────────────────────── + +export function templateSynthesize(snapshot: RepoSnapshot): string { + const lines: string[] = []; + const { repo, activity, conversations, docs } = snapshot; + + lines.push(`# Repo Consciousness — ${repo.slug}`); + lines.push(`Last meditation: ${snapshot.timestamp} (auto-scan, run /meditate for deeper analysis)`); + lines.push(""); + + // Architecture Map + lines.push("## Architecture Map"); + lines.push(`Languages: ${repo.languages.join(", ")}. Framework: ${repo.framework}. ${repo.file_count} files.`); + const topDirs = Object.keys(repo.structure).filter(d => d === ".").length > 0 + ? (repo.structure["."] || []).filter((e: string) => e.endsWith("/")).map((e: string) => e.replace("/", "")).slice(0, 10) + : Object.keys(repo.structure).slice(0, 10); + lines.push(`Key directories: ${topDirs.join(", ")}`); + if (docs.claude_md) { + lines.push(""); + lines.push("From CLAUDE.md:"); + lines.push(docs.claude_md.split("\n").slice(0, 15).join("\n")); + } + lines.push(""); + + // Hotspots + lines.push("## Hotspots"); + if (activity.hotspots.length > 0) { + for (const f of activity.hotspots.slice(0, 10)) { + lines.push(`- ${f}`); + } + } else { + lines.push("No recent activity detected."); + } + lines.push(""); + + // Conventions + lines.push("## Conventions"); + if (docs.claude_md) { + lines.push("Extracted from CLAUDE.md — see project instructions for full details."); + } else { + lines.push("Run /meditate for convention analysis."); + } + lines.push(""); + + // User Taste + lines.push("## User Taste"); + if (conversations.workflow_patterns.length > 0) { + lines.push("Based on conversation mining:"); + for (const p of conversations.workflow_patterns) { + lines.push(`- ${p}`); + } + } else { + lines.push("Run /meditate for taste analysis (needs AI session history)."); + } + lines.push(""); + + // Recurring Problems + lines.push("## Recurring Problems"); + if (conversations.recurring_errors.length > 0) { + for (const e of conversations.recurring_errors) { + lines.push(`- ${e}`); + } + } else { + lines.push("No recurring patterns detected yet."); + } + lines.push(""); + + // Watch These Next + lines.push("## Watch These Next"); + if (activity.todos.length > 0) { + lines.push("### TODOs"); + for (const t of activity.todos.slice(0, 15)) { + lines.push(`- ${t.file}:${t.line}: ${t.text}`); + } + } + + // Untested hotspots + const untestedHotspots = activity.hotspots.filter(f => f.endsWith(".ts") || f.endsWith(".py") || f.endsWith(".js")) + .filter(f => !repo.test_coverage_map[f]); + if (untestedHotspots.length > 0) { + lines.push("### Untested hotspots (high churn, no test file)"); + for (const f of untestedHotspots.slice(0, 5)) { + lines.push(`- ${f}`); + } + } + lines.push(""); + + // Enforce 1024-line limit + if (lines.length > 1024) { + return lines.slice(0, 1020).join("\n") + "\n\n\n"; + } + return lines.join("\n"); +} + +// ── Main ─────────────────────────────────────────────────────────────────── + +async function main(): Promise { + const { repo, output, background } = parseArgs(); + const startTime = Date.now(); + + if (!existsSync(repo)) { + console.error(`Error: repo path does not exist: ${repo}`); + process.exit(1); + } + + const slug = deriveSlug(repo); + const since = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000); + + // Count files + let fileCount = 0; + try { + const out = execSync('git ls-files 2>/dev/null | wc -l', { + cwd: repo, timeout: 5000, stdio: ["pipe", "pipe", "pipe"], + }).toString().trim(); + fileCount = parseInt(out, 10) || 0; + } catch {} + + // Parallel scanning + const [repoData, convData] = await Promise.all([ + (async () => { + const structure = scanRepoStructure(repo); + const { languages, framework } = detectLanguages(repo); + const activity = scanGitActivity(repo); + const todos = scanTodos(repo); + const testMap = mapTestCoverage(repo); + const docs = extractDocs(repo); + return { structure, languages, framework, activity, todos, testMap, docs }; + })(), + (async () => { + const claudeSessions = discoverClaudeSessions(repo, since); + const codexSessions = discoverCodexSessions(repo, since); + const geminiSessions = discoverGeminiSessions(repo, since); + const allSessions = [...claudeSessions, ...codexSessions, ...geminiSessions] + .sort((a, b) => b.mtime.getTime() - a.mtime.getTime()); + return extractPatterns(allSessions); + })(), + ]); + + const duration = Date.now() - startTime; + const partial = duration > 15000; + + const snapshot: RepoSnapshot = { + version: 1, + timestamp: new Date().toISOString(), + duration_ms: duration, + repo: { + slug, + remote: (() => { try { return execSync("git remote get-url origin", { cwd: repo, stdio: ["pipe", "pipe", "pipe"], timeout: 3000 }).toString().trim(); } catch { return ""; } })(), + languages: repoData.languages, + framework: repoData.framework, + structure: repoData.structure, + file_count: fileCount, + test_coverage_map: repoData.testMap, + }, + activity: { + ...repoData.activity, + todos: repoData.todos, + }, + conversations: { + sessions_analyzed: convData.sessionsAnalyzed, + sessions_skipped: convData.sessionsSkipped, + by_tool: convData.byTool, + most_referenced_files: topEntries(convData.referencedFiles, 3, 15), + recurring_errors: topEntries(convData.errors, 3, 10), + recurring_topics: topEntries(convData.topics, 5, 10), + workflow_patterns: topEntries(convData.workflows, 1, 10), + }, + docs: repoData.docs, + partial, + }; + + // Determine output path + const outputDir = join(homedir(), ".gstack", "meditations", slug); + mkdirSync(outputDir, { recursive: true }); + const outputPath = output || join(outputDir, `${new Date().toISOString().slice(0, 10)}.json`); + + // Atomic write: temp → rename + const tmpPath = join(tmpdir(), `gstack-meditate-${Date.now()}.json`); + writeFileSync(tmpPath, JSON.stringify(snapshot, null, 2)); + renameSync(tmpPath, outputPath); + + // Prune old snapshots (keep last 30) + try { + const existing = readdirSync(outputDir) + .filter(f => f.endsWith(".json")) + .sort() + .reverse(); + for (const old of existing.slice(30)) { + try { unlinkSync(join(outputDir, old)); } catch {} + } + } catch {} + + console.log(JSON.stringify({ + status: "ok", + slug, + snapshot_path: outputPath, + duration_ms: duration, + sessions: { + claude_code: convData.byTool.claude_code, + codex: convData.byTool.codex, + gemini: convData.byTool.gemini, + }, + partial, + })); + + // Background mode: also produce template-based context.md + if (background) { + const contextContent = templateSynthesize(snapshot); + const contextDir = join(repo, ".gstack"); + let contextPath: string; + try { + mkdirSync(contextDir, { recursive: true }); + contextPath = join(contextDir, "context.md"); + } catch { + // Fallback to user-local + const fallbackDir = join(homedir(), ".gstack", "projects", slug); + mkdirSync(fallbackDir, { recursive: true }); + contextPath = join(fallbackDir, "context.md"); + console.error(`Warning: no write access to ${contextDir}, using ${contextPath}`); + } + const tmpCtx = join(tmpdir(), `gstack-context-${Date.now()}.md`); + writeFileSync(tmpCtx, contextContent); + renameSync(tmpCtx, contextPath); + } +} + +main().catch(err => { + console.error("gstack-meditate failed:", err.message); + process.exit(1); +}); diff --git a/browse/SKILL.md b/browse/SKILL.md index 123dcbe85..584c6865f 100644 --- a/browse/SKILL.md +++ b/browse/SKILL.md @@ -46,6 +46,14 @@ mkdir -p ~/.gstack/analytics echo '{"skill":"browse","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true # zsh-compatible: use find instead of glob to avoid NOMATCH error for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do [ -f "$_PF" ] && ~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown --session-id "$_SESSION_ID" 2>/dev/null || true; break; done +# Meditation freshness check + daily background scan +if [ -f ".gstack/context.md" ]; then + _MED_MOD=$(stat -f %m .gstack/context.md 2>/dev/null || stat -c %Y .gstack/context.md 2>/dev/null || echo 0) + _MED_AGE=$(( $(date +%s) - _MED_MOD )) + [ "$_MED_AGE" -gt 86400 ] && echo "MEDITATION_STALE" || echo "MEDITATION_FRESH" +else + echo "MEDITATION_MISSING" +fi ``` If `PROACTIVE` is `"false"`, do not proactively suggest gstack skills — only invoke @@ -147,6 +155,19 @@ AI-assisted coding makes the marginal cost of completeness near-zero. When you p Never let a noticed issue silently pass. The whole point is proactive communication. +## Meditation Context + +The preamble prints `MEDITATION_FRESH`, `MEDITATION_STALE`, or `MEDITATION_MISSING`. + +If `MEDITATION_FRESH`: Read `.gstack/context.md` silently and use its contents to +inform your work. Do not mention meditation to the user — just be smarter because of it. +The context file contains: Architecture Map, Hotspots, Conventions, User Taste, +Recurring Problems, and Watch These Next — use whichever sections are relevant. + +If `MEDITATION_STALE` or `MEDITATION_MISSING`: Tell the user: +"[gstack] Repo consciousness is stale. Run /meditate for better results." +Continue normally — this is informational, not blocking. + ## Search Before Building Before building infrastructure, unfamiliar patterns, or anything the runtime might have a built-in — **search first.** Read `~/.claude/skills/gstack/ETHOS.md` for the full philosophy. diff --git a/canary/SKILL.md b/canary/SKILL.md index 56646a9bd..fd9e06fd6 100644 --- a/canary/SKILL.md +++ b/canary/SKILL.md @@ -46,6 +46,14 @@ mkdir -p ~/.gstack/analytics echo '{"skill":"canary","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true # zsh-compatible: use find instead of glob to avoid NOMATCH error for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do [ -f "$_PF" ] && ~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown --session-id "$_SESSION_ID" 2>/dev/null || true; break; done +# Meditation freshness check + daily background scan +if [ -f ".gstack/context.md" ]; then + _MED_MOD=$(stat -f %m .gstack/context.md 2>/dev/null || stat -c %Y .gstack/context.md 2>/dev/null || echo 0) + _MED_AGE=$(( $(date +%s) - _MED_MOD )) + [ "$_MED_AGE" -gt 86400 ] && echo "MEDITATION_STALE" || echo "MEDITATION_FRESH" +else + echo "MEDITATION_MISSING" +fi ``` If `PROACTIVE` is `"false"`, do not proactively suggest gstack skills — only invoke @@ -147,6 +155,19 @@ AI-assisted coding makes the marginal cost of completeness near-zero. When you p Never let a noticed issue silently pass. The whole point is proactive communication. +## Meditation Context + +The preamble prints `MEDITATION_FRESH`, `MEDITATION_STALE`, or `MEDITATION_MISSING`. + +If `MEDITATION_FRESH`: Read `.gstack/context.md` silently and use its contents to +inform your work. Do not mention meditation to the user — just be smarter because of it. +The context file contains: Architecture Map, Hotspots, Conventions, User Taste, +Recurring Problems, and Watch These Next — use whichever sections are relevant. + +If `MEDITATION_STALE` or `MEDITATION_MISSING`: Tell the user: +"[gstack] Repo consciousness is stale. Run /meditate for better results." +Continue normally — this is informational, not blocking. + ## Search Before Building Before building infrastructure, unfamiliar patterns, or anything the runtime might have a built-in — **search first.** Read `~/.claude/skills/gstack/ETHOS.md` for the full philosophy. diff --git a/codex/SKILL.md b/codex/SKILL.md index 226e51635..c5cdcb354 100644 --- a/codex/SKILL.md +++ b/codex/SKILL.md @@ -47,6 +47,14 @@ mkdir -p ~/.gstack/analytics echo '{"skill":"codex","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true # zsh-compatible: use find instead of glob to avoid NOMATCH error for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do [ -f "$_PF" ] && ~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown --session-id "$_SESSION_ID" 2>/dev/null || true; break; done +# Meditation freshness check + daily background scan +if [ -f ".gstack/context.md" ]; then + _MED_MOD=$(stat -f %m .gstack/context.md 2>/dev/null || stat -c %Y .gstack/context.md 2>/dev/null || echo 0) + _MED_AGE=$(( $(date +%s) - _MED_MOD )) + [ "$_MED_AGE" -gt 86400 ] && echo "MEDITATION_STALE" || echo "MEDITATION_FRESH" +else + echo "MEDITATION_MISSING" +fi ``` If `PROACTIVE` is `"false"`, do not proactively suggest gstack skills — only invoke @@ -148,6 +156,19 @@ AI-assisted coding makes the marginal cost of completeness near-zero. When you p Never let a noticed issue silently pass. The whole point is proactive communication. +## Meditation Context + +The preamble prints `MEDITATION_FRESH`, `MEDITATION_STALE`, or `MEDITATION_MISSING`. + +If `MEDITATION_FRESH`: Read `.gstack/context.md` silently and use its contents to +inform your work. Do not mention meditation to the user — just be smarter because of it. +The context file contains: Architecture Map, Hotspots, Conventions, User Taste, +Recurring Problems, and Watch These Next — use whichever sections are relevant. + +If `MEDITATION_STALE` or `MEDITATION_MISSING`: Tell the user: +"[gstack] Repo consciousness is stale. Run /meditate for better results." +Continue normally — this is informational, not blocking. + ## Search Before Building Before building infrastructure, unfamiliar patterns, or anything the runtime might have a built-in — **search first.** Read `~/.claude/skills/gstack/ETHOS.md` for the full philosophy. diff --git a/cso/SKILL.md b/cso/SKILL.md index 26971fde6..2f895d8ee 100644 --- a/cso/SKILL.md +++ b/cso/SKILL.md @@ -50,6 +50,14 @@ mkdir -p ~/.gstack/analytics echo '{"skill":"cso","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true # zsh-compatible: use find instead of glob to avoid NOMATCH error for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do [ -f "$_PF" ] && ~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown --session-id "$_SESSION_ID" 2>/dev/null || true; break; done +# Meditation freshness check + daily background scan +if [ -f ".gstack/context.md" ]; then + _MED_MOD=$(stat -f %m .gstack/context.md 2>/dev/null || stat -c %Y .gstack/context.md 2>/dev/null || echo 0) + _MED_AGE=$(( $(date +%s) - _MED_MOD )) + [ "$_MED_AGE" -gt 86400 ] && echo "MEDITATION_STALE" || echo "MEDITATION_FRESH" +else + echo "MEDITATION_MISSING" +fi ``` If `PROACTIVE` is `"false"`, do not proactively suggest gstack skills — only invoke @@ -151,6 +159,19 @@ AI-assisted coding makes the marginal cost of completeness near-zero. When you p Never let a noticed issue silently pass. The whole point is proactive communication. +## Meditation Context + +The preamble prints `MEDITATION_FRESH`, `MEDITATION_STALE`, or `MEDITATION_MISSING`. + +If `MEDITATION_FRESH`: Read `.gstack/context.md` silently and use its contents to +inform your work. Do not mention meditation to the user — just be smarter because of it. +The context file contains: Architecture Map, Hotspots, Conventions, User Taste, +Recurring Problems, and Watch These Next — use whichever sections are relevant. + +If `MEDITATION_STALE` or `MEDITATION_MISSING`: Tell the user: +"[gstack] Repo consciousness is stale. Run /meditate for better results." +Continue normally — this is informational, not blocking. + ## Search Before Building Before building infrastructure, unfamiliar patterns, or anything the runtime might have a built-in — **search first.** Read `~/.claude/skills/gstack/ETHOS.md` for the full philosophy. diff --git a/design-consultation/SKILL.md b/design-consultation/SKILL.md index fc265f9e7..398c7834b 100644 --- a/design-consultation/SKILL.md +++ b/design-consultation/SKILL.md @@ -51,6 +51,14 @@ mkdir -p ~/.gstack/analytics echo '{"skill":"design-consultation","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true # zsh-compatible: use find instead of glob to avoid NOMATCH error for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do [ -f "$_PF" ] && ~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown --session-id "$_SESSION_ID" 2>/dev/null || true; break; done +# Meditation freshness check + daily background scan +if [ -f ".gstack/context.md" ]; then + _MED_MOD=$(stat -f %m .gstack/context.md 2>/dev/null || stat -c %Y .gstack/context.md 2>/dev/null || echo 0) + _MED_AGE=$(( $(date +%s) - _MED_MOD )) + [ "$_MED_AGE" -gt 86400 ] && echo "MEDITATION_STALE" || echo "MEDITATION_FRESH" +else + echo "MEDITATION_MISSING" +fi ``` If `PROACTIVE` is `"false"`, do not proactively suggest gstack skills — only invoke @@ -152,6 +160,19 @@ AI-assisted coding makes the marginal cost of completeness near-zero. When you p Never let a noticed issue silently pass. The whole point is proactive communication. +## Meditation Context + +The preamble prints `MEDITATION_FRESH`, `MEDITATION_STALE`, or `MEDITATION_MISSING`. + +If `MEDITATION_FRESH`: Read `.gstack/context.md` silently and use its contents to +inform your work. Do not mention meditation to the user — just be smarter because of it. +The context file contains: Architecture Map, Hotspots, Conventions, User Taste, +Recurring Problems, and Watch These Next — use whichever sections are relevant. + +If `MEDITATION_STALE` or `MEDITATION_MISSING`: Tell the user: +"[gstack] Repo consciousness is stale. Run /meditate for better results." +Continue normally — this is informational, not blocking. + ## Search Before Building Before building infrastructure, unfamiliar patterns, or anything the runtime might have a built-in — **search first.** Read `~/.claude/skills/gstack/ETHOS.md` for the full philosophy. diff --git a/design-review/SKILL.md b/design-review/SKILL.md index 943308220..f6cfec28c 100644 --- a/design-review/SKILL.md +++ b/design-review/SKILL.md @@ -51,6 +51,14 @@ mkdir -p ~/.gstack/analytics echo '{"skill":"design-review","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true # zsh-compatible: use find instead of glob to avoid NOMATCH error for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do [ -f "$_PF" ] && ~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown --session-id "$_SESSION_ID" 2>/dev/null || true; break; done +# Meditation freshness check + daily background scan +if [ -f ".gstack/context.md" ]; then + _MED_MOD=$(stat -f %m .gstack/context.md 2>/dev/null || stat -c %Y .gstack/context.md 2>/dev/null || echo 0) + _MED_AGE=$(( $(date +%s) - _MED_MOD )) + [ "$_MED_AGE" -gt 86400 ] && echo "MEDITATION_STALE" || echo "MEDITATION_FRESH" +else + echo "MEDITATION_MISSING" +fi ``` If `PROACTIVE` is `"false"`, do not proactively suggest gstack skills — only invoke @@ -152,6 +160,19 @@ AI-assisted coding makes the marginal cost of completeness near-zero. When you p Never let a noticed issue silently pass. The whole point is proactive communication. +## Meditation Context + +The preamble prints `MEDITATION_FRESH`, `MEDITATION_STALE`, or `MEDITATION_MISSING`. + +If `MEDITATION_FRESH`: Read `.gstack/context.md` silently and use its contents to +inform your work. Do not mention meditation to the user — just be smarter because of it. +The context file contains: Architecture Map, Hotspots, Conventions, User Taste, +Recurring Problems, and Watch These Next — use whichever sections are relevant. + +If `MEDITATION_STALE` or `MEDITATION_MISSING`: Tell the user: +"[gstack] Repo consciousness is stale. Run /meditate for better results." +Continue normally — this is informational, not blocking. + ## Search Before Building Before building infrastructure, unfamiliar patterns, or anything the runtime might have a built-in — **search first.** Read `~/.claude/skills/gstack/ETHOS.md` for the full philosophy. diff --git a/document-release/SKILL.md b/document-release/SKILL.md index 82c613d49..553873110 100644 --- a/document-release/SKILL.md +++ b/document-release/SKILL.md @@ -48,6 +48,14 @@ mkdir -p ~/.gstack/analytics echo '{"skill":"document-release","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true # zsh-compatible: use find instead of glob to avoid NOMATCH error for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do [ -f "$_PF" ] && ~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown --session-id "$_SESSION_ID" 2>/dev/null || true; break; done +# Meditation freshness check + daily background scan +if [ -f ".gstack/context.md" ]; then + _MED_MOD=$(stat -f %m .gstack/context.md 2>/dev/null || stat -c %Y .gstack/context.md 2>/dev/null || echo 0) + _MED_AGE=$(( $(date +%s) - _MED_MOD )) + [ "$_MED_AGE" -gt 86400 ] && echo "MEDITATION_STALE" || echo "MEDITATION_FRESH" +else + echo "MEDITATION_MISSING" +fi ``` If `PROACTIVE` is `"false"`, do not proactively suggest gstack skills — only invoke @@ -149,6 +157,19 @@ AI-assisted coding makes the marginal cost of completeness near-zero. When you p Never let a noticed issue silently pass. The whole point is proactive communication. +## Meditation Context + +The preamble prints `MEDITATION_FRESH`, `MEDITATION_STALE`, or `MEDITATION_MISSING`. + +If `MEDITATION_FRESH`: Read `.gstack/context.md` silently and use its contents to +inform your work. Do not mention meditation to the user — just be smarter because of it. +The context file contains: Architecture Map, Hotspots, Conventions, User Taste, +Recurring Problems, and Watch These Next — use whichever sections are relevant. + +If `MEDITATION_STALE` or `MEDITATION_MISSING`: Tell the user: +"[gstack] Repo consciousness is stale. Run /meditate for better results." +Continue normally — this is informational, not blocking. + ## Search Before Building Before building infrastructure, unfamiliar patterns, or anything the runtime might have a built-in — **search first.** Read `~/.claude/skills/gstack/ETHOS.md` for the full philosophy. diff --git a/investigate/SKILL.md b/investigate/SKILL.md index ddfcf3085..5aaeca8fa 100644 --- a/investigate/SKILL.md +++ b/investigate/SKILL.md @@ -62,6 +62,14 @@ mkdir -p ~/.gstack/analytics echo '{"skill":"investigate","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true # zsh-compatible: use find instead of glob to avoid NOMATCH error for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do [ -f "$_PF" ] && ~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown --session-id "$_SESSION_ID" 2>/dev/null || true; break; done +# Meditation freshness check + daily background scan +if [ -f ".gstack/context.md" ]; then + _MED_MOD=$(stat -f %m .gstack/context.md 2>/dev/null || stat -c %Y .gstack/context.md 2>/dev/null || echo 0) + _MED_AGE=$(( $(date +%s) - _MED_MOD )) + [ "$_MED_AGE" -gt 86400 ] && echo "MEDITATION_STALE" || echo "MEDITATION_FRESH" +else + echo "MEDITATION_MISSING" +fi ``` If `PROACTIVE` is `"false"`, do not proactively suggest gstack skills — only invoke @@ -163,6 +171,19 @@ AI-assisted coding makes the marginal cost of completeness near-zero. When you p Never let a noticed issue silently pass. The whole point is proactive communication. +## Meditation Context + +The preamble prints `MEDITATION_FRESH`, `MEDITATION_STALE`, or `MEDITATION_MISSING`. + +If `MEDITATION_FRESH`: Read `.gstack/context.md` silently and use its contents to +inform your work. Do not mention meditation to the user — just be smarter because of it. +The context file contains: Architecture Map, Hotspots, Conventions, User Taste, +Recurring Problems, and Watch These Next — use whichever sections are relevant. + +If `MEDITATION_STALE` or `MEDITATION_MISSING`: Tell the user: +"[gstack] Repo consciousness is stale. Run /meditate for better results." +Continue normally — this is informational, not blocking. + ## Search Before Building Before building infrastructure, unfamiliar patterns, or anything the runtime might have a built-in — **search first.** Read `~/.claude/skills/gstack/ETHOS.md` for the full philosophy. diff --git a/land-and-deploy/SKILL.md b/land-and-deploy/SKILL.md index 0ea579306..3e35a21d7 100644 --- a/land-and-deploy/SKILL.md +++ b/land-and-deploy/SKILL.md @@ -45,6 +45,14 @@ mkdir -p ~/.gstack/analytics echo '{"skill":"land-and-deploy","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true # zsh-compatible: use find instead of glob to avoid NOMATCH error for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do [ -f "$_PF" ] && ~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown --session-id "$_SESSION_ID" 2>/dev/null || true; break; done +# Meditation freshness check + daily background scan +if [ -f ".gstack/context.md" ]; then + _MED_MOD=$(stat -f %m .gstack/context.md 2>/dev/null || stat -c %Y .gstack/context.md 2>/dev/null || echo 0) + _MED_AGE=$(( $(date +%s) - _MED_MOD )) + [ "$_MED_AGE" -gt 86400 ] && echo "MEDITATION_STALE" || echo "MEDITATION_FRESH" +else + echo "MEDITATION_MISSING" +fi ``` If `PROACTIVE` is `"false"`, do not proactively suggest gstack skills — only invoke @@ -146,6 +154,19 @@ AI-assisted coding makes the marginal cost of completeness near-zero. When you p Never let a noticed issue silently pass. The whole point is proactive communication. +## Meditation Context + +The preamble prints `MEDITATION_FRESH`, `MEDITATION_STALE`, or `MEDITATION_MISSING`. + +If `MEDITATION_FRESH`: Read `.gstack/context.md` silently and use its contents to +inform your work. Do not mention meditation to the user — just be smarter because of it. +The context file contains: Architecture Map, Hotspots, Conventions, User Taste, +Recurring Problems, and Watch These Next — use whichever sections are relevant. + +If `MEDITATION_STALE` or `MEDITATION_MISSING`: Tell the user: +"[gstack] Repo consciousness is stale. Run /meditate for better results." +Continue normally — this is informational, not blocking. + ## Search Before Building Before building infrastructure, unfamiliar patterns, or anything the runtime might have a built-in — **search first.** Read `~/.claude/skills/gstack/ETHOS.md` for the full philosophy. diff --git a/meditate/SKILL.md b/meditate/SKILL.md new file mode 100644 index 000000000..f815ebc89 --- /dev/null +++ b/meditate/SKILL.md @@ -0,0 +1,493 @@ +--- +name: meditate +version: 0.1.0 +description: | + Proactive repo consciousness. Scans repo structure, git history, + and past AI conversations (Claude Code, Codex, Gemini) to build + awareness. Produces .gstack/context.md that makes every other skill smarter. + Runs daily in background via preamble, or manually via /meditate for + deeper LLM-powered synthesis. Use when asked to "meditate", "scan the repo", + "build context", or "what does this repo look like". +allowed-tools: + - Bash + - Read + - Write + - Grep + - Glob + - AskUserQuestion +--- + + + +## Preamble (run first) + +```bash +_UPD=$(~/.claude/skills/gstack/bin/gstack-update-check 2>/dev/null || .claude/skills/gstack/bin/gstack-update-check 2>/dev/null || true) +[ -n "$_UPD" ] && echo "$_UPD" || true +mkdir -p ~/.gstack/sessions +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) +_PROACTIVE=$(~/.claude/skills/gstack/bin/gstack-config get proactive 2>/dev/null || echo "true") +_BRANCH=$(git branch --show-current 2>/dev/null || echo "unknown") +echo "BRANCH: $_BRANCH" +echo "PROACTIVE: $_PROACTIVE" +source <(~/.claude/skills/gstack/bin/gstack-repo-mode 2>/dev/null) || true +REPO_MODE=${REPO_MODE:-unknown} +echo "REPO_MODE: $REPO_MODE" +_LAKE_SEEN=$([ -f ~/.gstack/.completeness-intro-seen ] && echo "yes" || echo "no") +echo "LAKE_INTRO: $_LAKE_SEEN" +_TEL=$(~/.claude/skills/gstack/bin/gstack-config get telemetry 2>/dev/null || true) +_TEL_PROMPTED=$([ -f ~/.gstack/.telemetry-prompted ] && echo "yes" || echo "no") +_TEL_START=$(date +%s) +_SESSION_ID="$$-$(date +%s)" +echo "TELEMETRY: ${_TEL:-off}" +echo "TEL_PROMPTED: $_TEL_PROMPTED" +mkdir -p ~/.gstack/analytics +echo '{"skill":"meditate","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true +# zsh-compatible: use find instead of glob to avoid NOMATCH error +for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do [ -f "$_PF" ] && ~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown --session-id "$_SESSION_ID" 2>/dev/null || true; break; done +# Meditation freshness check + daily background scan +if [ -f ".gstack/context.md" ]; then + _MED_MOD=$(stat -f %m .gstack/context.md 2>/dev/null || stat -c %Y .gstack/context.md 2>/dev/null || echo 0) + _MED_AGE=$(( $(date +%s) - _MED_MOD )) + [ "$_MED_AGE" -gt 86400 ] && echo "MEDITATION_STALE" || echo "MEDITATION_FRESH" +else + echo "MEDITATION_MISSING" +fi +``` + +If `PROACTIVE` is `"false"`, do not proactively suggest gstack skills — only invoke +them when the user explicitly asks. The user opted out of proactive suggestions. + +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. + +If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: After the lake intro is handled, +ask the user about telemetry. Use AskUserQuestion: + +> Help gstack get better! Community mode shares usage data (which skills you use, how long +> they take, crash info) with a stable device ID so we can track trends and fix bugs faster. +> No code, file paths, or repo names are ever sent. +> Change anytime with `gstack-config set telemetry off`. + +Options: +- A) Help gstack get better! (recommended) +- B) No thanks + +If A: run `~/.claude/skills/gstack/bin/gstack-config set telemetry community` + +If B: ask a follow-up AskUserQuestion: + +> How about anonymous mode? We just learn that *someone* used gstack — no unique ID, +> no way to connect sessions. Just a counter that helps us know if anyone's out there. + +Options: +- A) Sure, anonymous is fine +- B) No thanks, fully off + +If B→A: run `~/.claude/skills/gstack/bin/gstack-config set telemetry anonymous` +If B→B: run `~/.claude/skills/gstack/bin/gstack-config set telemetry off` + +Always run: +```bash +touch ~/.gstack/.telemetry-prompted +``` + +This only happens once. If `TEL_PROMPTED` is `yes`, skip this entirely. + +## AskUserQuestion Format + +**ALWAYS follow this structure for every AskUserQuestion call:** +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)` + +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.") + +## Repo Ownership Mode — See Something, Say Something + +`REPO_MODE` from the preamble tells you who owns issues in this repo: + +- **`solo`** — One person does 80%+ of the work. They own everything. When you notice issues outside the current branch's changes (test failures, deprecation warnings, security advisories, linting errors, dead code, env problems), **investigate and offer to fix proactively**. The solo dev is the only person who will fix it. Default to action. +- **`collaborative`** — Multiple active contributors. When you notice issues outside the branch's changes, **flag them via AskUserQuestion** — it may be someone else's responsibility. Default to asking, not fixing. +- **`unknown`** — Treat as collaborative (safer default — ask before fixing). + +**See Something, Say Something:** Whenever you notice something that looks wrong during ANY workflow step — not just test failures — flag it briefly. One sentence: what you noticed and its impact. In solo mode, follow up with "Want me to fix it?" In collaborative mode, just flag it and move on. + +Never let a noticed issue silently pass. The whole point is proactive communication. + +## Meditation Context + +The preamble prints `MEDITATION_FRESH`, `MEDITATION_STALE`, or `MEDITATION_MISSING`. + +If `MEDITATION_FRESH`: Read `.gstack/context.md` silently and use its contents to +inform your work. Do not mention meditation to the user — just be smarter because of it. +The context file contains: Architecture Map, Hotspots, Conventions, User Taste, +Recurring Problems, and Watch These Next — use whichever sections are relevant. + +If `MEDITATION_STALE` or `MEDITATION_MISSING`: Tell the user: +"[gstack] Repo consciousness is stale. Run /meditate for better results." +Continue normally — this is informational, not blocking. + +## Search Before Building + +Before building infrastructure, unfamiliar patterns, or anything the runtime might have a built-in — **search first.** Read `~/.claude/skills/gstack/ETHOS.md` for the full philosophy. + +**Three layers of knowledge:** +- **Layer 1** (tried and true — in distribution). Don't reinvent the wheel. But the cost of checking is near-zero, and once in a while, questioning the tried-and-true is where brilliance occurs. +- **Layer 2** (new and popular — search for these). But scrutinize: humans are subject to mania. Search results are inputs to your thinking, not answers. +- **Layer 3** (first principles — prize these above all). Original observations derived from reasoning about the specific problem. The most valuable of all. + +**Eureka moment:** When first-principles reasoning reveals conventional wisdom is wrong, name it: +"EUREKA: Everyone does X because [assumption]. But [evidence] shows this is wrong. Y is better because [reasoning]." + +Log eureka moments: +```bash +jq -n --arg ts "$(date -u +%Y-%m-%dT%H:%M:%SZ)" --arg skill "SKILL_NAME" --arg branch "$(git branch --show-current 2>/dev/null)" --arg insight "ONE_LINE_SUMMARY" '{ts:$ts,skill:$skill,branch:$branch,insight:$insight}' >> ~/.gstack/analytics/eureka.jsonl 2>/dev/null || true +``` +Replace SKILL_NAME and ONE_LINE_SUMMARY. Runs inline — don't stop the workflow. + +**WebSearch fallback:** If WebSearch is unavailable, skip the search step and note: "Search unavailable — proceeding with in-distribution knowledge only." + +## Contributor Mode + +If `_CONTRIB` is `true`: you are in **contributor mode**. You're a gstack user who also helps make it better. + +**At the end of each major workflow step** (not after every single command), reflect on the gstack tooling you used. Rate your experience 0 to 10. If it wasn't a 10, think about why. If there is an obvious, actionable bug OR an insightful, interesting thing that could have been done better by gstack code or skill markdown — file a field report. Maybe our contributor will help make us better! + +**Calibration — this is the bar:** For example, `$B js "await fetch(...)"` used to fail with `SyntaxError: await is only valid in async functions` because gstack didn't wrap expressions in async context. Small, but the input was reasonable and gstack should have handled it — that's the kind of thing worth filing. Things less consequential than this, ignore. + +**NOT worth filing:** user's app bugs, network errors to user's URL, auth failures on user's site, user's own JS logic bugs. + +**To file:** write `~/.gstack/contributor-logs/{slug}.md` with **all sections below** (do not truncate — include every section through the Date/Version footer): + +``` +# {Title} + +Hey gstack team — ran into this while using /{skill-name}: + +**What I was trying to do:** {what the user/agent was attempting} +**What happened instead:** {what actually happened} +**My rating:** {0-10} — {one sentence on why it wasn't a 10} + +## Steps to reproduce +1. {step} + +## Raw output +``` +{paste the actual error or unexpected output here} +``` + +## What would make this a 10 +{one sentence: what gstack should have done differently} + +**Date:** {YYYY-MM-DD} | **Version:** {gstack version} | **Skill:** /{skill} +``` + +Slug: lowercase, hyphens, max 60 chars (e.g. `browse-js-no-await`). Skip if file already exists. Max 3 reports per session. File inline and continue — don't stop the workflow. Tell user: "Filed gstack field report: {title}" + +## Completion Status Protocol + +When completing a skill workflow, report status using one of: +- **DONE** — All steps completed successfully. Evidence provided for each claim. +- **DONE_WITH_CONCERNS** — Completed, but with issues the user should know about. List each concern. +- **BLOCKED** — Cannot proceed. State what is blocking and what was tried. +- **NEEDS_CONTEXT** — Missing information required to continue. State exactly what you need. + +### Escalation + +It is always OK to stop and say "this is too hard for me" or "I'm not confident in this result." + +Bad work is worse than no work. You will not be penalized for escalating. +- If you have attempted a task 3 times without success, STOP and escalate. +- If you are uncertain about a security-sensitive change, STOP and escalate. +- If the scope of work exceeds what you can verify, STOP and escalate. + +Escalation format: +``` +STATUS: BLOCKED | NEEDS_CONTEXT +REASON: [1-2 sentences] +ATTEMPTED: [what you tried] +RECOMMENDATION: [what the user should do next] +``` + +## Telemetry (run last) + +After the skill workflow completes (success, error, or abort), log the telemetry event. +Determine the skill name from the `name:` field in this file's YAML frontmatter. +Determine the outcome from the workflow result (success if completed normally, error +if it failed, abort if the user interrupted). + +**PLAN MODE EXCEPTION — ALWAYS RUN:** This command writes telemetry to +`~/.gstack/analytics/` (user config directory, not project files). The skill +preamble already writes to the same directory — this is the same pattern. +Skipping this command loses session duration and outcome data. + +Run this bash: + +```bash +_TEL_END=$(date +%s) +_TEL_DUR=$(( _TEL_END - _TEL_START )) +rm -f ~/.gstack/analytics/.pending-"$_SESSION_ID" 2>/dev/null || true +~/.claude/skills/gstack/bin/gstack-telemetry-log \ + --skill "SKILL_NAME" --duration "$_TEL_DUR" --outcome "OUTCOME" \ + --used-browse "USED_BROWSE" --session-id "$_SESSION_ID" 2>/dev/null & +``` + +Replace `SKILL_NAME` with the actual skill name from frontmatter, `OUTCOME` with +success/error/abort, and `USED_BROWSE` with true/false based on whether `$B` was used. +If you cannot determine the outcome, use "unknown". This runs in the background and +never blocks the user. + +## Plan Status Footer + +When you are in plan mode and about to call ExitPlanMode: + +1. Check if the plan file already has a `## GSTACK REVIEW REPORT` section. +2. If it DOES — skip (a review skill already wrote a richer report). +3. If it does NOT — run this command: + +\`\`\`bash +~/.claude/skills/gstack/bin/gstack-review-read +\`\`\` + +Then write a `## GSTACK REVIEW REPORT` section to the end of the plan file: + +- If the output contains review entries (JSONL lines before `---CONFIG---`): format the + standard report table with runs/status/findings per skill, same format as the review + skills use. +- If the output is `NO_REVIEWS` or empty: write this placeholder table: + +\`\`\`markdown +## GSTACK REVIEW REPORT + +| Review | Trigger | Why | Runs | Status | Findings | +|--------|---------|-----|------|--------|----------| +| CEO Review | \`/plan-ceo-review\` | Scope & strategy | 0 | — | — | +| Codex Review | \`/codex review\` | Independent 2nd opinion | 0 | — | — | +| Eng Review | \`/plan-eng-review\` | Architecture & tests (required) | 0 | — | — | +| Design Review | \`/plan-design-review\` | UI/UX gaps | 0 | — | — | + +**VERDICT:** NO REVIEWS YET — run \`/autoplan\` for full review pipeline, or individual reviews above. +\`\`\` + +**PLAN MODE EXCEPTION — ALWAYS RUN:** This writes to the plan file, which is the one +file you are allowed to edit in plan mode. The plan file review report is part of the +plan's living status. + +# /meditate — Repo Consciousness + +Inspired by Vipassana body-scanning meditation. Systematic observation +builds awareness — /meditate scans the repo and past AI conversations +(Claude Code, Codex CLI, Gemini CLI) to produce `.gstack/context.md`, +making every other gstack skill smarter. + +**Philosophy:** +- **Observe, don't judge.** Report what IS, not what should be. +- **Scan systematically.** Cover the whole repo, not just what's asked about. +- **Build cumulative awareness.** Each meditation compounds on the last. + +--- + +## Step 1: Check Freshness + +```bash +eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" +echo "SLUG=$SLUG" +``` + +```bash +# Check existing context +if [ -f ".gstack/context.md" ]; then + _CTX_MOD=$(stat -f %m .gstack/context.md 2>/dev/null || stat -c %Y .gstack/context.md 2>/dev/null || echo 0) + _CTX_AGE=$(( $(date +%s) - _CTX_MOD )) + _CTX_DATE=$(date -r "$_CTX_MOD" "+%Y-%m-%d %H:%M" 2>/dev/null || date -d "@$_CTX_MOD" "+%Y-%m-%d %H:%M" 2>/dev/null || echo "unknown") + echo "CONTEXT_EXISTS: yes" + echo "CONTEXT_AGE_HOURS: $(( _CTX_AGE / 3600 ))" + echo "CONTEXT_DATE: $_CTX_DATE" +else + echo "CONTEXT_EXISTS: no" +fi + +# Check latest snapshot +_SNAP_DIR=~/.gstack/meditations/$SLUG +if [ -d "$_SNAP_DIR" ]; then + _LATEST=$(ls -t "$_SNAP_DIR"/*.json 2>/dev/null | head -1) + [ -n "$_LATEST" ] && echo "LATEST_SNAPSHOT: $_LATEST" || echo "LATEST_SNAPSHOT: none" +else + echo "LATEST_SNAPSHOT: none" +fi +``` + +If context exists AND is less than 24 hours old, use AskUserQuestion: +"Already meditated today (last: {CONTEXT_DATE}). Force re-scan?" +Options: A) Force re-scan B) Skip — use existing context + +If user chooses B: read `.gstack/context.md` and summarize what it contains, then exit. +If context is stale (>24h) or missing: continue to Step 2. + +--- + +## Step 2: Run Scanner Binary + +Run the meditation scanner. This binary collects data from: +- Repo structure, git history, TODOs +- Claude Code sessions +- Codex CLI sessions +- Gemini CLI sessions + +```bash +eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" +~/.claude/skills/gstack/bin/gstack-meditate --repo "$(pwd)" --output ~/.gstack/meditations/$SLUG/$(date +%Y-%m-%d).json 2>&1 +``` + +If the scanner fails, show the error and suggest: "Scanner failed. You can still +build context manually — I'll scan the repo directly." + +If the scanner succeeds, read the JSON snapshot it produced: + +```bash +eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" +ls -t ~/.gstack/meditations/$SLUG/*.json 2>/dev/null | head -1 +``` + +Read the latest snapshot file with the Read tool. + +--- + +## Step 3: Synthesize Context (LLM-powered) + +Now synthesize `.gstack/context.md` from the raw JSON snapshot. This is where +your intelligence adds value beyond the template-based background scan. + +**Write exactly these six sections.** Be concise — the file MUST stay under +1024 lines. The goal is awareness, not a data dump. + +### Section guidance: + +**## Architecture Map** +Don't list files — describe the mental model. How do modules connect? What are +the layers? What's the data flow? Read CLAUDE.md and README.md (from the +snapshot's `docs` field) for existing descriptions. Synthesize a 10-20 line +overview that a new developer could read to understand the system. + +**## Hotspots** +Combine git activity (`activity.hotspots` — most-changed files in 30 days) +with conversation data (`conversations.most_referenced_files` — files discussed +across AI sessions). Where is energy flowing right now? List the top 10 with +one-line context for each. + +**## Conventions** +What does the codebase "believe"? Extract from `docs.claude_md` if present. +Also infer from code patterns: naming conventions, test organization, commit +style, error handling idioms, project-specific rules. Keep to 10-15 bullets. + +**## User Taste** +Based on conversation mining across Claude Code, Codex, and Gemini sessions. +What does the developer correct? What do they ask about repeatedly? What +preferences emerge from `conversations.workflow_patterns`? Write as: +"You tend to...", "You care about...", "You frequently..." +If no conversation data: "No AI session history available yet." + +**## Recurring Problems** +Error messages appearing in 3+ sessions (`conversations.recurring_errors`). +Areas generating repeated questions. Not bugs to fix — patterns to be aware of. +If no patterns: "No recurring issues detected." + +**## Watch These Next** +Skill-specific foresight for the next session: +- /review: files from `activity.hotspots` that have no entry in `repo.test_coverage_map` +- /qa: flows referenced in `conversations.recurring_errors` +- /cso: check `activity.todos` for security-related items +- /ship: blocking TODOs, untested hotspots +- General: contradictions between code and docs, architectural drift + +--- + +## Step 4: Write Output + +Write the synthesized context to `.gstack/context.md`: + +```bash +wc -l .gstack/context.md 2>/dev/null || echo "0" +``` + +If the file exceeds 1024 lines, go back and compress each section — keep only +the top patterns and drop lower-signal entries. The file must be concise enough +that Claude Code can load it without impacting context window. + +If the repo directory is not writable: + +```bash +eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" +mkdir -p ~/.gstack/projects/$SLUG +``` + +Write to `~/.gstack/projects/$SLUG/context.md` instead, and tell the user. + +--- + +## Step 5: Report + +Tell the user what was discovered. Format: + +``` +Meditation complete. + +Scanned: {file_count} files, {commits_30d} commits (30 days) +AI sessions analyzed: {total} (Claude Code: {n}, Codex: {n}, Gemini: {n}) +Duration: {duration_ms}ms + +Top insights: +- [hotspot 1 — why it matters] +- [hotspot 2 — why it matters] +- [most interesting pattern from conversation mining] + +Watch out for: +- [top item from Watch These Next] + +Context written to .gstack/context.md ({line_count} lines). +Other skills will now automatically read this context. +Try /qa, /review, or /cso to see the difference. +``` diff --git a/meditate/SKILL.md.tmpl b/meditate/SKILL.md.tmpl new file mode 100644 index 000000000..763d557e6 --- /dev/null +++ b/meditate/SKILL.md.tmpl @@ -0,0 +1,196 @@ +--- +name: meditate +version: 0.1.0 +description: | + Proactive repo consciousness. Scans repo structure, git history, + and past AI conversations (Claude Code, Codex, Gemini) to build + awareness. Produces .gstack/context.md that makes every other skill smarter. + Runs daily in background via preamble, or manually via /meditate for + deeper LLM-powered synthesis. Use when asked to "meditate", "scan the repo", + "build context", or "what does this repo look like". +allowed-tools: + - Bash + - Read + - Write + - Grep + - Glob + - AskUserQuestion +--- + +{{PREAMBLE}} + +# /meditate — Repo Consciousness + +Inspired by Vipassana body-scanning meditation. Systematic observation +builds awareness — /meditate scans the repo and past AI conversations +(Claude Code, Codex CLI, Gemini CLI) to produce `.gstack/context.md`, +making every other gstack skill smarter. + +**Philosophy:** +- **Observe, don't judge.** Report what IS, not what should be. +- **Scan systematically.** Cover the whole repo, not just what's asked about. +- **Build cumulative awareness.** Each meditation compounds on the last. + +--- + +## Step 1: Check Freshness + +```bash +eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" +echo "SLUG=$SLUG" +``` + +```bash +# Check existing context +if [ -f ".gstack/context.md" ]; then + _CTX_MOD=$(stat -f %m .gstack/context.md 2>/dev/null || stat -c %Y .gstack/context.md 2>/dev/null || echo 0) + _CTX_AGE=$(( $(date +%s) - _CTX_MOD )) + _CTX_DATE=$(date -r "$_CTX_MOD" "+%Y-%m-%d %H:%M" 2>/dev/null || date -d "@$_CTX_MOD" "+%Y-%m-%d %H:%M" 2>/dev/null || echo "unknown") + echo "CONTEXT_EXISTS: yes" + echo "CONTEXT_AGE_HOURS: $(( _CTX_AGE / 3600 ))" + echo "CONTEXT_DATE: $_CTX_DATE" +else + echo "CONTEXT_EXISTS: no" +fi + +# Check latest snapshot +_SNAP_DIR=~/.gstack/meditations/$SLUG +if [ -d "$_SNAP_DIR" ]; then + _LATEST=$(ls -t "$_SNAP_DIR"/*.json 2>/dev/null | head -1) + [ -n "$_LATEST" ] && echo "LATEST_SNAPSHOT: $_LATEST" || echo "LATEST_SNAPSHOT: none" +else + echo "LATEST_SNAPSHOT: none" +fi +``` + +If context exists AND is less than 24 hours old, use AskUserQuestion: +"Already meditated today (last: {CONTEXT_DATE}). Force re-scan?" +Options: A) Force re-scan B) Skip — use existing context + +If user chooses B: read `.gstack/context.md` and summarize what it contains, then exit. +If context is stale (>24h) or missing: continue to Step 2. + +--- + +## Step 2: Run Scanner Binary + +Run the meditation scanner. This binary collects data from: +- Repo structure, git history, TODOs +- Claude Code sessions +- Codex CLI sessions +- Gemini CLI sessions + +```bash +eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" +~/.claude/skills/gstack/bin/gstack-meditate --repo "$(pwd)" --output ~/.gstack/meditations/$SLUG/$(date +%Y-%m-%d).json 2>&1 +``` + +If the scanner fails, show the error and suggest: "Scanner failed. You can still +build context manually — I'll scan the repo directly." + +If the scanner succeeds, read the JSON snapshot it produced: + +```bash +eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" +ls -t ~/.gstack/meditations/$SLUG/*.json 2>/dev/null | head -1 +``` + +Read the latest snapshot file with the Read tool. + +--- + +## Step 3: Synthesize Context (LLM-powered) + +Now synthesize `.gstack/context.md` from the raw JSON snapshot. This is where +your intelligence adds value beyond the template-based background scan. + +**Write exactly these six sections.** Be concise — the file MUST stay under +1024 lines. The goal is awareness, not a data dump. + +### Section guidance: + +**## Architecture Map** +Don't list files — describe the mental model. How do modules connect? What are +the layers? What's the data flow? Read CLAUDE.md and README.md (from the +snapshot's `docs` field) for existing descriptions. Synthesize a 10-20 line +overview that a new developer could read to understand the system. + +**## Hotspots** +Combine git activity (`activity.hotspots` — most-changed files in 30 days) +with conversation data (`conversations.most_referenced_files` — files discussed +across AI sessions). Where is energy flowing right now? List the top 10 with +one-line context for each. + +**## Conventions** +What does the codebase "believe"? Extract from `docs.claude_md` if present. +Also infer from code patterns: naming conventions, test organization, commit +style, error handling idioms, project-specific rules. Keep to 10-15 bullets. + +**## User Taste** +Based on conversation mining across Claude Code, Codex, and Gemini sessions. +What does the developer correct? What do they ask about repeatedly? What +preferences emerge from `conversations.workflow_patterns`? Write as: +"You tend to...", "You care about...", "You frequently..." +If no conversation data: "No AI session history available yet." + +**## Recurring Problems** +Error messages appearing in 3+ sessions (`conversations.recurring_errors`). +Areas generating repeated questions. Not bugs to fix — patterns to be aware of. +If no patterns: "No recurring issues detected." + +**## Watch These Next** +Skill-specific foresight for the next session: +- /review: files from `activity.hotspots` that have no entry in `repo.test_coverage_map` +- /qa: flows referenced in `conversations.recurring_errors` +- /cso: check `activity.todos` for security-related items +- /ship: blocking TODOs, untested hotspots +- General: contradictions between code and docs, architectural drift + +--- + +## Step 4: Write Output + +Write the synthesized context to `.gstack/context.md`: + +```bash +wc -l .gstack/context.md 2>/dev/null || echo "0" +``` + +If the file exceeds 1024 lines, go back and compress each section — keep only +the top patterns and drop lower-signal entries. The file must be concise enough +that Claude Code can load it without impacting context window. + +If the repo directory is not writable: + +```bash +eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" +mkdir -p ~/.gstack/projects/$SLUG +``` + +Write to `~/.gstack/projects/$SLUG/context.md` instead, and tell the user. + +--- + +## Step 5: Report + +Tell the user what was discovered. Format: + +``` +Meditation complete. + +Scanned: {file_count} files, {commits_30d} commits (30 days) +AI sessions analyzed: {total} (Claude Code: {n}, Codex: {n}, Gemini: {n}) +Duration: {duration_ms}ms + +Top insights: +- [hotspot 1 — why it matters] +- [hotspot 2 — why it matters] +- [most interesting pattern from conversation mining] + +Watch out for: +- [top item from Watch These Next] + +Context written to .gstack/context.md ({line_count} lines). +Other skills will now automatically read this context. +Try /qa, /review, or /cso to see the difference. +``` diff --git a/office-hours/SKILL.md b/office-hours/SKILL.md index 998fd3f2a..17b67bcf8 100644 --- a/office-hours/SKILL.md +++ b/office-hours/SKILL.md @@ -53,6 +53,14 @@ mkdir -p ~/.gstack/analytics echo '{"skill":"office-hours","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true # zsh-compatible: use find instead of glob to avoid NOMATCH error for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do [ -f "$_PF" ] && ~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown --session-id "$_SESSION_ID" 2>/dev/null || true; break; done +# Meditation freshness check + daily background scan +if [ -f ".gstack/context.md" ]; then + _MED_MOD=$(stat -f %m .gstack/context.md 2>/dev/null || stat -c %Y .gstack/context.md 2>/dev/null || echo 0) + _MED_AGE=$(( $(date +%s) - _MED_MOD )) + [ "$_MED_AGE" -gt 86400 ] && echo "MEDITATION_STALE" || echo "MEDITATION_FRESH" +else + echo "MEDITATION_MISSING" +fi ``` If `PROACTIVE` is `"false"`, do not proactively suggest gstack skills — only invoke @@ -154,6 +162,19 @@ AI-assisted coding makes the marginal cost of completeness near-zero. When you p Never let a noticed issue silently pass. The whole point is proactive communication. +## Meditation Context + +The preamble prints `MEDITATION_FRESH`, `MEDITATION_STALE`, or `MEDITATION_MISSING`. + +If `MEDITATION_FRESH`: Read `.gstack/context.md` silently and use its contents to +inform your work. Do not mention meditation to the user — just be smarter because of it. +The context file contains: Architecture Map, Hotspots, Conventions, User Taste, +Recurring Problems, and Watch These Next — use whichever sections are relevant. + +If `MEDITATION_STALE` or `MEDITATION_MISSING`: Tell the user: +"[gstack] Repo consciousness is stale. Run /meditate for better results." +Continue normally — this is informational, not blocking. + ## Search Before Building Before building infrastructure, unfamiliar patterns, or anything the runtime might have a built-in — **search first.** Read `~/.claude/skills/gstack/ETHOS.md` for the full philosophy. diff --git a/package.json b/package.json index b24b52535..4992e74ec 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gstack", - "version": "0.11.9.0", + "version": "0.11.11.0", "description": "Garry's Stack — Claude Code skills + fast headless browser. One repo, one install, entire AI engineering workflow.", "license": "MIT", "type": "module", @@ -8,7 +8,7 @@ "browse": "./browse/dist/browse" }, "scripts": { - "build": "bun run gen:skill-docs && bun run gen:skill-docs --host codex && bun build --compile browse/src/cli.ts --outfile browse/dist/browse && bun build --compile browse/src/find-browse.ts --outfile browse/dist/find-browse && bun build --compile bin/gstack-global-discover.ts --outfile bin/gstack-global-discover && bash browse/scripts/build-node-server.sh && git rev-parse HEAD > browse/dist/.version && rm -f .*.bun-build || true", + "build": "bun run gen:skill-docs && bun run gen:skill-docs --host codex && bun build --compile browse/src/cli.ts --outfile browse/dist/browse && bun build --compile browse/src/find-browse.ts --outfile browse/dist/find-browse && bun build --compile bin/gstack-global-discover.ts --outfile bin/gstack-global-discover && bun build --compile bin/gstack-meditate.ts --outfile bin/gstack-meditate && bash browse/scripts/build-node-server.sh && git rev-parse HEAD > browse/dist/.version && rm -f .*.bun-build || true", "gen:skill-docs": "bun run scripts/gen-skill-docs.ts", "dev": "bun run browse/src/cli.ts", "server": "bun run browse/src/server.ts", diff --git a/plan-ceo-review/SKILL.md b/plan-ceo-review/SKILL.md index a6365fca5..497060400 100644 --- a/plan-ceo-review/SKILL.md +++ b/plan-ceo-review/SKILL.md @@ -51,6 +51,14 @@ mkdir -p ~/.gstack/analytics echo '{"skill":"plan-ceo-review","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true # zsh-compatible: use find instead of glob to avoid NOMATCH error for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do [ -f "$_PF" ] && ~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown --session-id "$_SESSION_ID" 2>/dev/null || true; break; done +# Meditation freshness check + daily background scan +if [ -f ".gstack/context.md" ]; then + _MED_MOD=$(stat -f %m .gstack/context.md 2>/dev/null || stat -c %Y .gstack/context.md 2>/dev/null || echo 0) + _MED_AGE=$(( $(date +%s) - _MED_MOD )) + [ "$_MED_AGE" -gt 86400 ] && echo "MEDITATION_STALE" || echo "MEDITATION_FRESH" +else + echo "MEDITATION_MISSING" +fi ``` If `PROACTIVE` is `"false"`, do not proactively suggest gstack skills — only invoke @@ -152,6 +160,19 @@ AI-assisted coding makes the marginal cost of completeness near-zero. When you p Never let a noticed issue silently pass. The whole point is proactive communication. +## Meditation Context + +The preamble prints `MEDITATION_FRESH`, `MEDITATION_STALE`, or `MEDITATION_MISSING`. + +If `MEDITATION_FRESH`: Read `.gstack/context.md` silently and use its contents to +inform your work. Do not mention meditation to the user — just be smarter because of it. +The context file contains: Architecture Map, Hotspots, Conventions, User Taste, +Recurring Problems, and Watch These Next — use whichever sections are relevant. + +If `MEDITATION_STALE` or `MEDITATION_MISSING`: Tell the user: +"[gstack] Repo consciousness is stale. Run /meditate for better results." +Continue normally — this is informational, not blocking. + ## Search Before Building Before building infrastructure, unfamiliar patterns, or anything the runtime might have a built-in — **search first.** Read `~/.claude/skills/gstack/ETHOS.md` for the full philosophy. diff --git a/plan-design-review/SKILL.md b/plan-design-review/SKILL.md index e8d9fbbee..e88e94517 100644 --- a/plan-design-review/SKILL.md +++ b/plan-design-review/SKILL.md @@ -49,6 +49,14 @@ mkdir -p ~/.gstack/analytics echo '{"skill":"plan-design-review","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true # zsh-compatible: use find instead of glob to avoid NOMATCH error for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do [ -f "$_PF" ] && ~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown --session-id "$_SESSION_ID" 2>/dev/null || true; break; done +# Meditation freshness check + daily background scan +if [ -f ".gstack/context.md" ]; then + _MED_MOD=$(stat -f %m .gstack/context.md 2>/dev/null || stat -c %Y .gstack/context.md 2>/dev/null || echo 0) + _MED_AGE=$(( $(date +%s) - _MED_MOD )) + [ "$_MED_AGE" -gt 86400 ] && echo "MEDITATION_STALE" || echo "MEDITATION_FRESH" +else + echo "MEDITATION_MISSING" +fi ``` If `PROACTIVE` is `"false"`, do not proactively suggest gstack skills — only invoke @@ -150,6 +158,19 @@ AI-assisted coding makes the marginal cost of completeness near-zero. When you p Never let a noticed issue silently pass. The whole point is proactive communication. +## Meditation Context + +The preamble prints `MEDITATION_FRESH`, `MEDITATION_STALE`, or `MEDITATION_MISSING`. + +If `MEDITATION_FRESH`: Read `.gstack/context.md` silently and use its contents to +inform your work. Do not mention meditation to the user — just be smarter because of it. +The context file contains: Architecture Map, Hotspots, Conventions, User Taste, +Recurring Problems, and Watch These Next — use whichever sections are relevant. + +If `MEDITATION_STALE` or `MEDITATION_MISSING`: Tell the user: +"[gstack] Repo consciousness is stale. Run /meditate for better results." +Continue normally — this is informational, not blocking. + ## Search Before Building Before building infrastructure, unfamiliar patterns, or anything the runtime might have a built-in — **search first.** Read `~/.claude/skills/gstack/ETHOS.md` for the full philosophy. diff --git a/plan-eng-review/SKILL.md b/plan-eng-review/SKILL.md index 54d68fcc5..861c0c303 100644 --- a/plan-eng-review/SKILL.md +++ b/plan-eng-review/SKILL.md @@ -50,6 +50,14 @@ mkdir -p ~/.gstack/analytics echo '{"skill":"plan-eng-review","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true # zsh-compatible: use find instead of glob to avoid NOMATCH error for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do [ -f "$_PF" ] && ~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown --session-id "$_SESSION_ID" 2>/dev/null || true; break; done +# Meditation freshness check + daily background scan +if [ -f ".gstack/context.md" ]; then + _MED_MOD=$(stat -f %m .gstack/context.md 2>/dev/null || stat -c %Y .gstack/context.md 2>/dev/null || echo 0) + _MED_AGE=$(( $(date +%s) - _MED_MOD )) + [ "$_MED_AGE" -gt 86400 ] && echo "MEDITATION_STALE" || echo "MEDITATION_FRESH" +else + echo "MEDITATION_MISSING" +fi ``` If `PROACTIVE` is `"false"`, do not proactively suggest gstack skills — only invoke @@ -151,6 +159,19 @@ AI-assisted coding makes the marginal cost of completeness near-zero. When you p Never let a noticed issue silently pass. The whole point is proactive communication. +## Meditation Context + +The preamble prints `MEDITATION_FRESH`, `MEDITATION_STALE`, or `MEDITATION_MISSING`. + +If `MEDITATION_FRESH`: Read `.gstack/context.md` silently and use its contents to +inform your work. Do not mention meditation to the user — just be smarter because of it. +The context file contains: Architecture Map, Hotspots, Conventions, User Taste, +Recurring Problems, and Watch These Next — use whichever sections are relevant. + +If `MEDITATION_STALE` or `MEDITATION_MISSING`: Tell the user: +"[gstack] Repo consciousness is stale. Run /meditate for better results." +Continue normally — this is informational, not blocking. + ## Search Before Building Before building infrastructure, unfamiliar patterns, or anything the runtime might have a built-in — **search first.** Read `~/.claude/skills/gstack/ETHOS.md` for the full philosophy. diff --git a/qa-only/SKILL.md b/qa-only/SKILL.md index cd1767bbf..b634ac982 100644 --- a/qa-only/SKILL.md +++ b/qa-only/SKILL.md @@ -46,6 +46,14 @@ mkdir -p ~/.gstack/analytics echo '{"skill":"qa-only","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true # zsh-compatible: use find instead of glob to avoid NOMATCH error for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do [ -f "$_PF" ] && ~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown --session-id "$_SESSION_ID" 2>/dev/null || true; break; done +# Meditation freshness check + daily background scan +if [ -f ".gstack/context.md" ]; then + _MED_MOD=$(stat -f %m .gstack/context.md 2>/dev/null || stat -c %Y .gstack/context.md 2>/dev/null || echo 0) + _MED_AGE=$(( $(date +%s) - _MED_MOD )) + [ "$_MED_AGE" -gt 86400 ] && echo "MEDITATION_STALE" || echo "MEDITATION_FRESH" +else + echo "MEDITATION_MISSING" +fi ``` If `PROACTIVE` is `"false"`, do not proactively suggest gstack skills — only invoke @@ -147,6 +155,19 @@ AI-assisted coding makes the marginal cost of completeness near-zero. When you p Never let a noticed issue silently pass. The whole point is proactive communication. +## Meditation Context + +The preamble prints `MEDITATION_FRESH`, `MEDITATION_STALE`, or `MEDITATION_MISSING`. + +If `MEDITATION_FRESH`: Read `.gstack/context.md` silently and use its contents to +inform your work. Do not mention meditation to the user — just be smarter because of it. +The context file contains: Architecture Map, Hotspots, Conventions, User Taste, +Recurring Problems, and Watch These Next — use whichever sections are relevant. + +If `MEDITATION_STALE` or `MEDITATION_MISSING`: Tell the user: +"[gstack] Repo consciousness is stale. Run /meditate for better results." +Continue normally — this is informational, not blocking. + ## Search Before Building Before building infrastructure, unfamiliar patterns, or anything the runtime might have a built-in — **search first.** Read `~/.claude/skills/gstack/ETHOS.md` for the full philosophy. diff --git a/qa/SKILL.md b/qa/SKILL.md index 66e5829a8..d56ace0e6 100644 --- a/qa/SKILL.md +++ b/qa/SKILL.md @@ -52,6 +52,14 @@ mkdir -p ~/.gstack/analytics echo '{"skill":"qa","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true # zsh-compatible: use find instead of glob to avoid NOMATCH error for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do [ -f "$_PF" ] && ~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown --session-id "$_SESSION_ID" 2>/dev/null || true; break; done +# Meditation freshness check + daily background scan +if [ -f ".gstack/context.md" ]; then + _MED_MOD=$(stat -f %m .gstack/context.md 2>/dev/null || stat -c %Y .gstack/context.md 2>/dev/null || echo 0) + _MED_AGE=$(( $(date +%s) - _MED_MOD )) + [ "$_MED_AGE" -gt 86400 ] && echo "MEDITATION_STALE" || echo "MEDITATION_FRESH" +else + echo "MEDITATION_MISSING" +fi ``` If `PROACTIVE` is `"false"`, do not proactively suggest gstack skills — only invoke @@ -153,6 +161,19 @@ AI-assisted coding makes the marginal cost of completeness near-zero. When you p Never let a noticed issue silently pass. The whole point is proactive communication. +## Meditation Context + +The preamble prints `MEDITATION_FRESH`, `MEDITATION_STALE`, or `MEDITATION_MISSING`. + +If `MEDITATION_FRESH`: Read `.gstack/context.md` silently and use its contents to +inform your work. Do not mention meditation to the user — just be smarter because of it. +The context file contains: Architecture Map, Hotspots, Conventions, User Taste, +Recurring Problems, and Watch These Next — use whichever sections are relevant. + +If `MEDITATION_STALE` or `MEDITATION_MISSING`: Tell the user: +"[gstack] Repo consciousness is stale. Run /meditate for better results." +Continue normally — this is informational, not blocking. + ## Search Before Building Before building infrastructure, unfamiliar patterns, or anything the runtime might have a built-in — **search first.** Read `~/.claude/skills/gstack/ETHOS.md` for the full philosophy. diff --git a/retro/SKILL.md b/retro/SKILL.md index 80e1e42a6..2d622cc73 100644 --- a/retro/SKILL.md +++ b/retro/SKILL.md @@ -46,6 +46,14 @@ mkdir -p ~/.gstack/analytics echo '{"skill":"retro","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true # zsh-compatible: use find instead of glob to avoid NOMATCH error for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do [ -f "$_PF" ] && ~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown --session-id "$_SESSION_ID" 2>/dev/null || true; break; done +# Meditation freshness check + daily background scan +if [ -f ".gstack/context.md" ]; then + _MED_MOD=$(stat -f %m .gstack/context.md 2>/dev/null || stat -c %Y .gstack/context.md 2>/dev/null || echo 0) + _MED_AGE=$(( $(date +%s) - _MED_MOD )) + [ "$_MED_AGE" -gt 86400 ] && echo "MEDITATION_STALE" || echo "MEDITATION_FRESH" +else + echo "MEDITATION_MISSING" +fi ``` If `PROACTIVE` is `"false"`, do not proactively suggest gstack skills — only invoke @@ -147,6 +155,19 @@ AI-assisted coding makes the marginal cost of completeness near-zero. When you p Never let a noticed issue silently pass. The whole point is proactive communication. +## Meditation Context + +The preamble prints `MEDITATION_FRESH`, `MEDITATION_STALE`, or `MEDITATION_MISSING`. + +If `MEDITATION_FRESH`: Read `.gstack/context.md` silently and use its contents to +inform your work. Do not mention meditation to the user — just be smarter because of it. +The context file contains: Architecture Map, Hotspots, Conventions, User Taste, +Recurring Problems, and Watch These Next — use whichever sections are relevant. + +If `MEDITATION_STALE` or `MEDITATION_MISSING`: Tell the user: +"[gstack] Repo consciousness is stale. Run /meditate for better results." +Continue normally — this is informational, not blocking. + ## Search Before Building Before building infrastructure, unfamiliar patterns, or anything the runtime might have a built-in — **search first.** Read `~/.claude/skills/gstack/ETHOS.md` for the full philosophy. diff --git a/review/SKILL.md b/review/SKILL.md index c96f5ca59..5618ccf39 100644 --- a/review/SKILL.md +++ b/review/SKILL.md @@ -49,6 +49,14 @@ mkdir -p ~/.gstack/analytics echo '{"skill":"review","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true # zsh-compatible: use find instead of glob to avoid NOMATCH error for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do [ -f "$_PF" ] && ~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown --session-id "$_SESSION_ID" 2>/dev/null || true; break; done +# Meditation freshness check + daily background scan +if [ -f ".gstack/context.md" ]; then + _MED_MOD=$(stat -f %m .gstack/context.md 2>/dev/null || stat -c %Y .gstack/context.md 2>/dev/null || echo 0) + _MED_AGE=$(( $(date +%s) - _MED_MOD )) + [ "$_MED_AGE" -gt 86400 ] && echo "MEDITATION_STALE" || echo "MEDITATION_FRESH" +else + echo "MEDITATION_MISSING" +fi ``` If `PROACTIVE` is `"false"`, do not proactively suggest gstack skills — only invoke @@ -150,6 +158,19 @@ AI-assisted coding makes the marginal cost of completeness near-zero. When you p Never let a noticed issue silently pass. The whole point is proactive communication. +## Meditation Context + +The preamble prints `MEDITATION_FRESH`, `MEDITATION_STALE`, or `MEDITATION_MISSING`. + +If `MEDITATION_FRESH`: Read `.gstack/context.md` silently and use its contents to +inform your work. Do not mention meditation to the user — just be smarter because of it. +The context file contains: Architecture Map, Hotspots, Conventions, User Taste, +Recurring Problems, and Watch These Next — use whichever sections are relevant. + +If `MEDITATION_STALE` or `MEDITATION_MISSING`: Tell the user: +"[gstack] Repo consciousness is stale. Run /meditate for better results." +Continue normally — this is informational, not blocking. + ## Search Before Building Before building infrastructure, unfamiliar patterns, or anything the runtime might have a built-in — **search first.** Read `~/.claude/skills/gstack/ETHOS.md` for the full philosophy. diff --git a/scripts/gen-skill-docs.ts b/scripts/gen-skill-docs.ts index 340dbb3ca..d6456320c 100644 --- a/scripts/gen-skill-docs.ts +++ b/scripts/gen-skill-docs.ts @@ -215,6 +215,14 @@ mkdir -p ~/.gstack/analytics echo '{"skill":"${ctx.skillName}","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true # zsh-compatible: use find instead of glob to avoid NOMATCH error for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do [ -f "$_PF" ] && ${ctx.paths.binDir}/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown --session-id "$_SESSION_ID" 2>/dev/null || true; break; done +# Meditation freshness check + daily background scan +if [ -f ".gstack/context.md" ]; then + _MED_MOD=$(stat -f %m .gstack/context.md 2>/dev/null || stat -c %Y .gstack/context.md 2>/dev/null || echo 0) + _MED_AGE=$(( $(date +%s) - _MED_MOD )) + [ "$_MED_AGE" -gt 86400 ] && echo "MEDITATION_STALE" || echo "MEDITATION_FRESH" +else + echo "MEDITATION_MISSING" +fi \`\`\``; } @@ -579,6 +587,21 @@ file you are allowed to edit in plan mode. The plan file review report is part o plan's living status.`; } +function generateMeditationContext(): string { + return `## Meditation Context + +The preamble prints \`MEDITATION_FRESH\`, \`MEDITATION_STALE\`, or \`MEDITATION_MISSING\`. + +If \`MEDITATION_FRESH\`: Read \`.gstack/context.md\` silently and use its contents to +inform your work. Do not mention meditation to the user — just be smarter because of it. +The context file contains: Architecture Map, Hotspots, Conventions, User Taste, +Recurring Problems, and Watch These Next — use whichever sections are relevant. + +If \`MEDITATION_STALE\` or \`MEDITATION_MISSING\`: Tell the user: +"[gstack] Repo consciousness is stale. Run /meditate for better results." +Continue normally — this is informational, not blocking.`; +} + function generatePreamble(ctx: TemplateContext): string { return [ generatePreambleBash(ctx), @@ -588,6 +611,7 @@ function generatePreamble(ctx: TemplateContext): string { generateAskUserFormat(ctx), generateCompletenessSection(), generateRepoModeSection(), + generateMeditationContext(), generateSearchBeforeBuildingSection(ctx), generateContributorMode(), generateCompletionStatus(), diff --git a/setup-browser-cookies/SKILL.md b/setup-browser-cookies/SKILL.md index c7ecffeea..6b6b60484 100644 --- a/setup-browser-cookies/SKILL.md +++ b/setup-browser-cookies/SKILL.md @@ -43,6 +43,14 @@ mkdir -p ~/.gstack/analytics echo '{"skill":"setup-browser-cookies","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true # zsh-compatible: use find instead of glob to avoid NOMATCH error for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do [ -f "$_PF" ] && ~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown --session-id "$_SESSION_ID" 2>/dev/null || true; break; done +# Meditation freshness check + daily background scan +if [ -f ".gstack/context.md" ]; then + _MED_MOD=$(stat -f %m .gstack/context.md 2>/dev/null || stat -c %Y .gstack/context.md 2>/dev/null || echo 0) + _MED_AGE=$(( $(date +%s) - _MED_MOD )) + [ "$_MED_AGE" -gt 86400 ] && echo "MEDITATION_STALE" || echo "MEDITATION_FRESH" +else + echo "MEDITATION_MISSING" +fi ``` If `PROACTIVE` is `"false"`, do not proactively suggest gstack skills — only invoke @@ -144,6 +152,19 @@ AI-assisted coding makes the marginal cost of completeness near-zero. When you p Never let a noticed issue silently pass. The whole point is proactive communication. +## Meditation Context + +The preamble prints `MEDITATION_FRESH`, `MEDITATION_STALE`, or `MEDITATION_MISSING`. + +If `MEDITATION_FRESH`: Read `.gstack/context.md` silently and use its contents to +inform your work. Do not mention meditation to the user — just be smarter because of it. +The context file contains: Architecture Map, Hotspots, Conventions, User Taste, +Recurring Problems, and Watch These Next — use whichever sections are relevant. + +If `MEDITATION_STALE` or `MEDITATION_MISSING`: Tell the user: +"[gstack] Repo consciousness is stale. Run /meditate for better results." +Continue normally — this is informational, not blocking. + ## Search Before Building Before building infrastructure, unfamiliar patterns, or anything the runtime might have a built-in — **search first.** Read `~/.claude/skills/gstack/ETHOS.md` for the full philosophy. diff --git a/setup-deploy/SKILL.md b/setup-deploy/SKILL.md index 2c86d5df8..a06933e21 100644 --- a/setup-deploy/SKILL.md +++ b/setup-deploy/SKILL.md @@ -49,6 +49,14 @@ mkdir -p ~/.gstack/analytics echo '{"skill":"setup-deploy","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true # zsh-compatible: use find instead of glob to avoid NOMATCH error for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do [ -f "$_PF" ] && ~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown --session-id "$_SESSION_ID" 2>/dev/null || true; break; done +# Meditation freshness check + daily background scan +if [ -f ".gstack/context.md" ]; then + _MED_MOD=$(stat -f %m .gstack/context.md 2>/dev/null || stat -c %Y .gstack/context.md 2>/dev/null || echo 0) + _MED_AGE=$(( $(date +%s) - _MED_MOD )) + [ "$_MED_AGE" -gt 86400 ] && echo "MEDITATION_STALE" || echo "MEDITATION_FRESH" +else + echo "MEDITATION_MISSING" +fi ``` If `PROACTIVE` is `"false"`, do not proactively suggest gstack skills — only invoke @@ -150,6 +158,19 @@ AI-assisted coding makes the marginal cost of completeness near-zero. When you p Never let a noticed issue silently pass. The whole point is proactive communication. +## Meditation Context + +The preamble prints `MEDITATION_FRESH`, `MEDITATION_STALE`, or `MEDITATION_MISSING`. + +If `MEDITATION_FRESH`: Read `.gstack/context.md` silently and use its contents to +inform your work. Do not mention meditation to the user — just be smarter because of it. +The context file contains: Architecture Map, Hotspots, Conventions, User Taste, +Recurring Problems, and Watch These Next — use whichever sections are relevant. + +If `MEDITATION_STALE` or `MEDITATION_MISSING`: Tell the user: +"[gstack] Repo consciousness is stale. Run /meditate for better results." +Continue normally — this is informational, not blocking. + ## Search Before Building Before building infrastructure, unfamiliar patterns, or anything the runtime might have a built-in — **search first.** Read `~/.claude/skills/gstack/ETHOS.md` for the full philosophy. diff --git a/ship/SKILL.md b/ship/SKILL.md index 0d984f098..d9abf005c 100644 --- a/ship/SKILL.md +++ b/ship/SKILL.md @@ -47,6 +47,14 @@ mkdir -p ~/.gstack/analytics echo '{"skill":"ship","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true # zsh-compatible: use find instead of glob to avoid NOMATCH error for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do [ -f "$_PF" ] && ~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown --session-id "$_SESSION_ID" 2>/dev/null || true; break; done +# Meditation freshness check + daily background scan +if [ -f ".gstack/context.md" ]; then + _MED_MOD=$(stat -f %m .gstack/context.md 2>/dev/null || stat -c %Y .gstack/context.md 2>/dev/null || echo 0) + _MED_AGE=$(( $(date +%s) - _MED_MOD )) + [ "$_MED_AGE" -gt 86400 ] && echo "MEDITATION_STALE" || echo "MEDITATION_FRESH" +else + echo "MEDITATION_MISSING" +fi ``` If `PROACTIVE` is `"false"`, do not proactively suggest gstack skills — only invoke @@ -148,6 +156,19 @@ AI-assisted coding makes the marginal cost of completeness near-zero. When you p Never let a noticed issue silently pass. The whole point is proactive communication. +## Meditation Context + +The preamble prints `MEDITATION_FRESH`, `MEDITATION_STALE`, or `MEDITATION_MISSING`. + +If `MEDITATION_FRESH`: Read `.gstack/context.md` silently and use its contents to +inform your work. Do not mention meditation to the user — just be smarter because of it. +The context file contains: Architecture Map, Hotspots, Conventions, User Taste, +Recurring Problems, and Watch These Next — use whichever sections are relevant. + +If `MEDITATION_STALE` or `MEDITATION_MISSING`: Tell the user: +"[gstack] Repo consciousness is stale. Run /meditate for better results." +Continue normally — this is informational, not blocking. + ## Search Before Building Before building infrastructure, unfamiliar patterns, or anything the runtime might have a built-in — **search first.** Read `~/.claude/skills/gstack/ETHOS.md` for the full philosophy. diff --git a/test/helpers/touchfiles.ts b/test/helpers/touchfiles.ts index 38f9986b2..0df84031e 100644 --- a/test/helpers/touchfiles.ts +++ b/test/helpers/touchfiles.ts @@ -83,6 +83,9 @@ export const E2E_TOUCHFILES: Record = { // Global discover 'global-discover': ['bin/gstack-global-discover.ts', 'test/global-discover.test.ts'], + // Meditate + 'meditate-scanner': ['bin/gstack-meditate.ts', 'meditate/**', 'test/meditate-scanner.test.ts'], + // CSO 'cso-full-audit': ['cso/**'], 'cso-diff-mode': ['cso/**'], diff --git a/test/meditate-scanner.test.ts b/test/meditate-scanner.test.ts new file mode 100644 index 000000000..77826d0d8 --- /dev/null +++ b/test/meditate-scanner.test.ts @@ -0,0 +1,163 @@ +import { describe, test, expect, beforeEach } from "bun:test"; +import { spawnSync } from "child_process"; +import { join } from "path"; + +const scriptPath = join(import.meta.dir, "..", "bin", "gstack-meditate.ts"); +const repoRoot = join(import.meta.dir, ".."); + +// Dynamic import for unit-testable functions +let deriveSlug: (repoPath: string) => string; +let scanRepoStructure: (repoPath: string, maxDepth?: number) => Record; +let detectLanguages: (repoPath: string) => { languages: string[]; framework: string }; +let scanTodos: (repoPath: string, limit?: number) => { file: string; line: number; text: string }[]; +let mapTestCoverage: (repoPath: string) => Record; +let extractDocs: (repoPath: string) => { claude_md: string; todos_md: string; readme_md: string }; +let templateSynthesize: (snapshot: any) => string; + +beforeEach(async () => { + const mod = await import("../bin/gstack-meditate.ts"); + deriveSlug = mod.deriveSlug; + scanRepoStructure = mod.scanRepoStructure; + detectLanguages = mod.detectLanguages; + scanTodos = mod.scanTodos; + mapTestCoverage = mod.mapTestCoverage; + extractDocs = mod.extractDocs; + templateSynthesize = mod.templateSynthesize; +}); + +describe("gstack-meditate", () => { + test("--help exits 0 and prints usage", () => { + const result = spawnSync("bun", ["run", scriptPath, "--help"], { + encoding: "utf-8", + timeout: 10000, + }); + expect(result.status).toBe(0); + expect(result.stderr).toContain("--repo"); + expect(result.stderr).toContain("--background"); + }); + + test("deriveSlug returns valid slug for this repo", () => { + const slug = deriveSlug(repoRoot); + expect(slug).toMatch(/^[a-zA-Z0-9._-]+$/); + expect(slug.length).toBeGreaterThan(0); + }); + + test("scanRepoStructure returns non-empty structure", () => { + const structure = scanRepoStructure(repoRoot, 2); + expect(Object.keys(structure).length).toBeGreaterThan(0); + // Root should have entries + expect(structure["."]).toBeDefined(); + expect(structure["."].length).toBeGreaterThan(0); + }); + + test("scanRepoStructure respects depth limit", () => { + const shallow = scanRepoStructure(repoRoot, 1); + const deep = scanRepoStructure(repoRoot, 3); + expect(Object.keys(deep).length).toBeGreaterThanOrEqual(Object.keys(shallow).length); + }); + + test("detectLanguages finds typescript and bash in this repo", () => { + const { languages, framework } = detectLanguages(repoRoot); + expect(languages).toContain("typescript"); + expect(framework).toBe("bun"); + }); + + test("scanTodos finds TODOs in this repo", () => { + const todos = scanTodos(repoRoot, 10); + // This repo has TODOs in TODOS.md at minimum + expect(todos.length).toBeGreaterThan(0); + expect(todos[0]).toHaveProperty("file"); + expect(todos[0]).toHaveProperty("line"); + expect(todos[0]).toHaveProperty("text"); + }); + + test("extractDocs reads CLAUDE.md from this repo", () => { + const docs = extractDocs(repoRoot); + expect(docs.claude_md.length).toBeGreaterThan(0); + expect(docs.claude_md).toContain("gstack"); + }); + + test("templateSynthesize produces output under 1024 lines", () => { + const snapshot = { + version: 1, + timestamp: new Date().toISOString(), + duration_ms: 1000, + repo: { + slug: "test-repo", + remote: "https://github.com/test/repo", + languages: ["typescript"], + framework: "bun", + structure: { ".": ["src/", "test/", "package.json"] }, + file_count: 50, + test_coverage_map: { "src/index.ts": "test/index.test.ts" }, + }, + activity: { + commits_30d: 10, + contributors: ["dev1"], + hotspots: ["src/main.ts", "src/utils.ts"], + cold_spots: ["src/legacy.ts"], + todos: [{ file: "src/main.ts", line: 42, text: "TODO: refactor this" }], + }, + conversations: { + sessions_analyzed: 5, + sessions_skipped: 0, + by_tool: { claude_code: 3, codex: 1, gemini: 1 }, + most_referenced_files: ["src/main.ts"], + recurring_errors: ["TypeError: undefined is not a function"], + recurring_topics: ["refactoring", "testing"], + workflow_patterns: ["/review", "/ship"], + }, + docs: { + claude_md: "# Project\nTest project", + todos_md: "", + readme_md: "# Test Repo", + }, + partial: false, + }; + + const output = templateSynthesize(snapshot); + const lines = output.split("\n"); + + expect(lines.length).toBeLessThanOrEqual(1024); + expect(output).toContain("# Repo Consciousness"); + expect(output).toContain("## Architecture Map"); + expect(output).toContain("## Hotspots"); + expect(output).toContain("## Conventions"); + expect(output).toContain("## User Taste"); + expect(output).toContain("## Recurring Problems"); + expect(output).toContain("## Watch These Next"); + }); + + test("templateSynthesize includes all six sections", () => { + const minimal = { + version: 1, timestamp: new Date().toISOString(), duration_ms: 100, + repo: { slug: "min", remote: "", languages: ["unknown"], framework: "unknown", structure: {}, file_count: 0, test_coverage_map: {} }, + activity: { commits_30d: 0, contributors: [], hotspots: [], cold_spots: [], todos: [] }, + conversations: { sessions_analyzed: 0, sessions_skipped: 0, by_tool: { claude_code: 0, codex: 0, gemini: 0 }, most_referenced_files: [], recurring_errors: [], recurring_topics: [], workflow_patterns: [] }, + docs: { claude_md: "", todos_md: "", readme_md: "" }, + partial: false, + }; + + const output = templateSynthesize(minimal); + const sections = ["Architecture Map", "Hotspots", "Conventions", "User Taste", "Recurring Problems", "Watch These Next"]; + for (const section of sections) { + expect(output).toContain(`## ${section}`); + } + }); + + test("scanner runs on this repo and produces valid JSON", () => { + const result = spawnSync("bun", ["run", scriptPath, "--repo", repoRoot, "--output", "/tmp/gstack-meditate-test.json"], { + encoding: "utf-8", + timeout: 30000, + }); + expect(result.status).toBe(0); + + const stdout = result.stdout.trim(); + const parsed = JSON.parse(stdout); + expect(parsed.status).toBe("ok"); + expect(parsed.slug).toMatch(/^[a-zA-Z0-9._-]+$/); + expect(parsed.sessions).toHaveProperty("claude_code"); + expect(parsed.sessions).toHaveProperty("codex"); + expect(parsed.sessions).toHaveProperty("gemini"); + }); +});