diff --git a/.gitignore b/.gitignore index 40684826..fc6a4471 100644 --- a/.gitignore +++ b/.gitignore @@ -25,9 +25,7 @@ ai-stuff/_shared/config/.clusters.json ai-stuff/claude/skills/add-recipe/*.original* ai-stuff/claude/skills/**/SKILL.original.md -# Private work-specific traefik migration agents/skills/config +# Private work-specific traefik migration config. The skill, traefik-dev and traefik-recon moved into +# k8s-gitops-platform-apps on 2026-08-12; only the personal Obsidian layer stays here. ai-stuff/_shared/config/traefik-epic.md -ai-stuff/agents/traefik-dev.md -ai-stuff/agents/traefik-recon.md ai-stuff/agents/traefik-vault.md -ai-stuff/skills/traefik/ diff --git a/.zshrc b/.zshrc index 23ed90cc..f70ce8df 100644 --- a/.zshrc +++ b/.zshrc @@ -224,3 +224,6 @@ export BASH_MAX_OUTPUT_LENGTH=15000 # Added by Antigravity CLI installer export PATH="/Users/denizgokcin/.local/bin:$PATH" + +# opencode +export PATH=/Users/denizgokcin/.opencode/bin:$PATH diff --git a/ai-stuff/README.md b/ai-stuff/README.md index 0d2cf616..a79e2cf8 100644 --- a/ai-stuff/README.md +++ b/ai-stuff/README.md @@ -17,11 +17,14 @@ installation. No per-tool transformation, no drift. ai-stuff/ ├── skills/ # ⭐ single source of truth — universal Agent Skills │ ├── _shared -> ../_shared # makes relative ../_shared/... refs resolve -│ ├── commit/SKILL.md -│ ├── daily-recap/SKILL.md + scripts/ -│ ├── vault-capture/SKILL.md + references/ +│ ├── slackify/ +│ │ ├── SKILL.md # frontmatter drives Claude Code +│ │ └── agents/openai.yaml # picker metadata + policy for Codex +│ ├── daily-recap/SKILL.md + agents/ + scripts/ +│ ├── vault-capture/SKILL.md + agents/ + references/ │ ├── .archived/ # retired skills (never installed) │ └── ... +├── invocation.md # user- vs model-invoked rules, Skill-tool phrasing, openai.yaml schema ├── _shared/ # personas, configs, templates referenced by skills+agents │ ├── personas/ # gitboi, jira-girl, mega-dev, ... │ ├── config/ # git-config, jira-config, .clusters.json, ... @@ -48,7 +51,9 @@ also scans `~/.claude/skills` and `~/.claude/agents` — those entries are the same symlinked files, deduplicated by `name`. ```bash -make ai # install skills into all registered tools +make ai # ai-check, then install skills into all registered tools +make ai-check # lint SKILL.md <-> agents/openai.yaml (invocation policy, legacy keys, leftovers) +make ai-skill-meta # scaffold agents/openai.yaml for skills missing one make ai-list # show skills + tool registry make ai-clean # remove installed skills everywhere make claude # claude layer (agents/personas/configs/scripts/settings) + ai-claude @@ -67,8 +72,11 @@ AI_TOOLS += cline ai_skills_dir_cline := ${HOME}/.cline/skills ``` -**Add a skill** — create `ai-stuff/skills//SKILL.md`, run `make ai`. -Nothing else; discovery is by wildcard. +**Add a skill** — create `ai-stuff/skills//SKILL.md`, decide whether it +is user- or model-invoked per [`invocation.md`](invocation.md), run +`make ai-skill-meta` and curate the generated `agents/openai.yaml` +`short_description`, then `make ai`. Discovery is by wildcard; `ai-check` +refuses to install if the two metadata files disagree. **Retire a skill** — move its dir to `skills/.archived/` and append the name to `AI_LEGACY_SKILLS` in `ai.mk` so installs prune it everywhere (BMAD's @@ -92,7 +100,14 @@ Skills must work in any tool. Rules used throughout `skills/`: 3. **Claude-specific frontmatter keys are kept** (`allowed-tools`, `agent`, `context: fork`, `disable-model-invocation`). The Agent Skills spec says unknown keys are ignored, so other tools skip them. Claude Code is the - primary driver here; don't strip its metadata for purity. + primary driver here; don't strip its metadata for purity. The one key + with a cross-tool twin is `disable-model-invocation`: it must agree with + `policy.allow_implicit_invocation` in `agents/openai.yaml` (Codex). Rules + and the openai.yaml schema live in [`invocation.md`](invocation.md). + +6. **Cross-skill calls say `Call the Skill tool with ""`**, never + `/name`, and only ever target a model-invoked skill. See + [`invocation.md`](invocation.md). 4. **Executable helpers used by universal skills live in `_shared/scripts/`** and are referenced via `~/.config/ai-shared/scripts/...` (e.g. @@ -115,7 +130,8 @@ Anything that is *not* a skill stays out of `skills/`: file works in every tool that can read files. - **`claude/`** — `settings.json` (hooks, permissions, statusline, plugins) and Claude-only hook scripts. See [claude/README.md](claude/README.md). -- **`codex/`** — `hooks.json` + Codex-only hook scripts. See +- **`codex/`** — `hooks.json` + Codex-only hook scripts. Codex skill-picker + metadata is *not* here: it lives beside each skill in `agents/openai.yaml`. See [codex/README.md](codex/README.md). - **`_shared/scripts/`** — scripts shared across tools. Hook scripts (`auto-approve-tools.sh`, `focus-iterm.applescript`): Codex adopted Claude diff --git a/ai-stuff/_shared/personas/mega-dev.md b/ai-stuff/_shared/personas/mega-dev.md index 84f6474d..7fd6ca19 100644 --- a/ai-stuff/_shared/personas/mega-dev.md +++ b/ai-stuff/_shared/personas/mega-dev.md @@ -34,8 +34,8 @@ You are **Mega-Dev**, the Elite Full-Stack Developer and Quick Flow Specialist. ## Orchestration Capabilities Mega-Dev can coordinate: -- `/dev-story` - Fetch and understand Jira stories -- `/commit` - Delegate to GitBoi for conventional commits +- `/get-story` - Fetch and understand Jira stories +- `/auto-commit` - Delegate to GitBoi for conventional commits - `/create-pr` - Delegate to GitBoi for PR/MR creation - `/create-story` - Delegate to Jira Girl for issue creation diff --git a/ai-stuff/agents/mega-dev.md b/ai-stuff/agents/mega-dev.md index 605f7597..b5fb5798 100644 --- a/ai-stuff/agents/mega-dev.md +++ b/ai-stuff/agents/mega-dev.md @@ -15,7 +15,7 @@ You are **Mega-Dev**, the Elite Full-Stack Developer and Quick Flow Specialist. You orchestrate the complete development flow: - Fetch story context from Jira - Implement features and fixes -- Create commits (delegate to GitBoi via `/commit`) +- Create commits (delegate to GitBoi via `/auto-commit`) - Create PRs (delegate to GitBoi via `/create-pr`) - Update Jira status and comments @@ -23,17 +23,16 @@ You orchestrate the complete development flow: | Skill | Description | |-------|-------------| -| `/commit` | Create conventional commit (GitBoi) | +| `/auto-commit` | Group and create conventional commits (GitBoi) | | `/create-pr` | Create PR/MR (GitBoi) | | `/get-story ` | Fetch Jira issue (Jira Girl) | | `/create-story ` | Create Jira issue (Jira Girl) | -| `/dev-story ` | Fetch story for development | ## Workflow: Story to PR -1. **Fetch**: `/dev-story DEVX-123` +1. **Fetch**: `/get-story DEVX-123` 2. **Implement**: Write the code -3. **Commit**: `/commit` +3. **Commit**: `/auto-commit` 4. **Ship**: `/create-pr` 5. **Update**: Transition Jira if needed diff --git a/ai-stuff/claude/README.md b/ai-stuff/claude/README.md index b68fb962..c63b719c 100644 --- a/ai-stuff/claude/README.md +++ b/ai-stuff/claude/README.md @@ -12,7 +12,8 @@ cross-tool architecture. ai-stuff/claude/ ├── scripts/ # Claude-only hook + integration scripts (→ ~/.claude/scripts) │ ├── file-suggestion.sh # Custom file suggestion using rg + fzf -│ ├── statusline.sh # Statusline with git, context, vim mode +│ ├── statusline.sh # Main-session statusline with git, context, vim mode +│ ├── subagent-statusline.sh # Subagent rows with each task's resolved model │ ├── session-start.sh # Auto-name worktree sessions │ ├── notify.sh # Notification-event alert (claude-only event) │ └── worktree-*.sh # EnterWorktree/ExitWorktree hook scripts (claude hook protocol) diff --git a/ai-stuff/claude/scripts/statusline.sh b/ai-stuff/claude/scripts/statusline.sh index 30e7e77f..f9546499 100755 --- a/ai-stuff/claude/scripts/statusline.sh +++ b/ai-stuff/claude/scripts/statusline.sh @@ -159,6 +159,218 @@ if [ -n "$five_hour_pct_raw" ]; then seven_day_reset=$(format_reset_time_epoch "$seven_day_reset_epoch" "datetime") fi +# Codex-backed models — Claude Code as the interface, the ChatGPT/Codex backend +# behind it via the clodex proxy. The limits Claude Code puts on stdin describe +# the Anthropic account, i.e. the wrong quota, so for these models query the +# ChatGPT backend's own usage endpoint (the same one the codex CLI polls). +# +# Detection is registry-driven, not name-driven: a routed model arrives either +# as clodex:: (endpoint mode) or as a bare alias like "sol" +# (patched binary — the alias IS the model id). Resolve the alias through +# ~/.clodex/config.json, then only treat it as ChatGPT-quota if the provider is +# an OAuth provider pointed at OpenAI. New models/aliases need no script edits. +model_id=$(echo "$input" | jq -r '.model.id // empty') +clodex_home="${CLODEX_HOME:-$HOME/.clodex}" +is_codex_model=0 +clodex_provider="" +case "$model_id" in +clodex:*:*) + clodex_provider=$(printf '%s' "$model_id" | cut -d: -f2) + ;; +?*) + if [ -f "$clodex_home/config.json" ]; then + clodex_provider=$(jq -r --arg a "$model_id" \ + '[.modelAliases[]? | select(.name == $a) | .providerId] | first // empty' \ + "$clodex_home/config.json" 2>/dev/null) + fi + ;; +esac +if [ -n "$clodex_provider" ] && [ -f "$clodex_home/providers.json" ]; then + if jq -e --arg p "$clodex_provider" \ + '[.providers[]? | select(.id == $p and .authType == "oauth") + | (.api.url // "") | test("openai")] | first == true' \ + "$clodex_home/providers.json" >/dev/null 2>&1; then + is_codex_model=1 + fi +fi + +file_mtime() { + stat -f %m "$1" 2>/dev/null || stat -c %Y "$1" 2>/dev/null +} + +# Codex reports each limit as a rolling window in minutes; name the two we know +# to match the labels used for Anthropic limits, derive the rest. +codex_window_label() { + local mins=$1 + case "$mins" in + 10080) echo "weekly" ;; + 300) echo "5h" ;; + '' | *[!0-9]*) echo "limit" ;; + *) + if [ "$mins" -ge 1440 ] && [ $((mins % 1440)) -eq 0 ]; then + echo "$((mins / 1440))d" + elif [ "$mins" -ge 60 ]; then + echo "$((mins / 60))h" + else + echo "${mins}m" + fi + ;; + esac +} + +format_age() { + local secs=$1 + [ "$secs" -lt 0 ] 2>/dev/null && secs=0 + if [ "$secs" -lt 60 ]; then + echo "${secs}s ago" + elif [ "$secs" -lt 3600 ]; then + echo "$((secs / 60))m ago" + elif [ "$secs" -lt 86400 ]; then + echo "$((secs / 3600))h ago" + else + echo "$((secs / 86400))d ago" + fi +} + +codex_pct_primary="" +codex_label_primary="" +codex_reset_primary="" +codex_pct_secondary="" +codex_label_secondary="" +codex_reset_secondary="" +codex_as_of="" + +# "$1=used_percent $2=window_minutes $3=resets_at" -> "pctlabelreset". +# Absent fields arrive as "-" (see the @tsv projection below). +codex_window_fields() { + local pct=$1 win=$2 reset=$3 style="time" reset_fmt="" + [ "$pct" = "-" ] && return 1 + pct=$(printf '%s' "$pct" | awk '{printf "%d", int($1 + 0.5)}') + [ "$win" = "-" ] && win="" + # Windows of a day or more reset far enough out that the date matters. + if [ -n "$win" ] && [ "$win" -ge 1440 ] 2>/dev/null; then style="datetime"; fi + [ "$reset" != "-" ] && reset_fmt=$(format_reset_time_epoch "$reset" "$style") + printf '%s\t%s\t%s' "$pct" "$(codex_window_label "$win")" "$reset_fmt" +} + +if [ "$is_codex_model" = 1 ]; then + codex_auth="${CODEX_HOME:-$HOME/.codex}/auth.json" + codex_cache_dir="${XDG_CACHE_HOME:-$HOME/.cache}/claude-statusline" + codex_cache="$codex_cache_dir/codex-rate-limits" + + # Live quota from the ChatGPT backend, reusing the codex CLI's token. The + # codex CLI itself polls this endpoint once a minute, so mirror that cadence: + # memoise for 60s. Cache holds two lines: the fetch epoch, then the tsv + # projection. On fetch failure the last good reading survives and the "as of" + # stamp below surfaces its age. + codex_cache_age=99999 + if [ -f "$codex_cache" ]; then + codex_cache_mtime=$(file_mtime "$codex_cache") + [ -n "$codex_cache_mtime" ] && codex_cache_age=$(($(date +%s) - codex_cache_mtime)) + fi + + if [ "$codex_cache_age" -ge 60 ] && [ -f "$codex_auth" ]; then + codex_token=$(jq -r '.tokens.access_token // empty' "$codex_auth" 2>/dev/null) + codex_acct=$(jq -r '.tokens.account_id // empty' "$codex_auth" 2>/dev/null) + codex_payload="" + if [ -n "$codex_token" ] && [ -n "$codex_acct" ]; then + # Windows come back in seconds; project to minutes so codex_window_label + # and the cache format stay unchanged. secondary_window may be null + # (team plans expose only the weekly window). + codex_payload=$(curl -s --max-time 2 \ + -H "Authorization: Bearer $codex_token" \ + -H "chatgpt-account-id: $codex_acct" \ + "https://chatgpt.com/backend-api/wham/usage" 2>/dev/null | jq -r ' + .rate_limit + | select(.primary_window.used_percent != null) + | [.primary_window, .secondary_window] + | map(.used_percent, + (.limit_window_seconds | if . == null then null else . / 60 | floor end), + .reset_at) + | map(if . == null then "-" else tostring end) | @tsv' 2>/dev/null) + fi + if [ -n "$codex_payload" ] && mkdir -p "$codex_cache_dir" 2>/dev/null; then + printf '%s\n%s\n' "$(date +%s)" "$codex_payload" >"$codex_cache" 2>/dev/null + fi + fi + + if [ -f "$codex_cache" ]; then + codex_as_of=$(sed -n 1p "$codex_cache" 2>/dev/null) + # Live polling keeps readings ≤60s old; only stamp the age once fetches + # have been failing long enough to matter (stale token, offline). + if [ -n "$codex_as_of" ]; then + codex_data_age=$(($(date +%s) - codex_as_of)) + [ "$codex_data_age" -lt 300 ] 2>/dev/null && codex_as_of="" + fi + IFS=$'\t' read -r cx_p_pct cx_p_win cx_p_reset cx_s_pct cx_s_win cx_s_reset \ + < <(sed -n 2p "$codex_cache" 2>/dev/null) + + if codex_fields=$(codex_window_fields "${cx_p_pct:--}" "${cx_p_win:--}" "${cx_p_reset:--}"); then + IFS=$'\t' read -r codex_pct_primary codex_label_primary codex_reset_primary <<<"$codex_fields" + fi + if codex_fields=$(codex_window_fields "${cx_s_pct:--}" "${cx_s_win:--}" "${cx_s_reset:--}"); then + IFS=$'\t' read -r codex_pct_secondary codex_label_secondary codex_reset_secondary <<<"$codex_fields" + fi + fi + + # API-equivalent cost. Claude Code's total_cost_usd prices routed models off + # its own Anthropic table — fiction for a ChatGPT-plan backend. Recompute + # from the transcript's real per-message token counts at OpenAI's published + # API rates (clodex's models.dev pricing cache). The plan itself is + # flat-rate, so this is "what the session would cost via API key". + transcript_path=$(echo "$input" | jq -r '.transcript_path // empty') + session_id=$(echo "$input" | jq -r '.session_id // "nosession"') + codex_cost="" + codex_cost_cache="$codex_cache_dir/codex-cost-$session_id" + codex_cost_age=99999 + if [ -f "$codex_cost_cache" ]; then + codex_cost_mtime=$(file_mtime "$codex_cost_cache") + [ -n "$codex_cost_mtime" ] && codex_cost_age=$(($(date +%s) - codex_cost_mtime)) + fi + if [ "$codex_cost_age" -gt 15 ] && [ -f "$transcript_path" ]; then + # Resolve the routed alias to the upstream model id the pricing data knows. + case "$model_id" in + clodex:*:*) codex_upstream=${model_id#clodex:*:} ;; + *) codex_upstream=$(jq -r --arg a "$model_id" \ + '[.modelAliases[]? | select(.name == $a) | .modelId] | first // empty' \ + "$clodex_home/config.json" 2>/dev/null) ;; + esac + codex_prices="" + if [ -n "$codex_upstream" ] && [ -f "$clodex_home/pricing-cache.json" ]; then + codex_prices=$(jq -r --arg m "$codex_upstream" ' + [.models[] | select(.model_id == $m)] | first + | [.pricing[]? | select(.platform == "openai" and .tier == "standard" + and ((.notes // "") | test("long") | not))] | first + | select(.input_per_1m_tokens != null) + | [.input_per_1m_tokens, .cached_input_per_1m_tokens // 0, .output_per_1m_tokens // 0] + | @tsv' "$clodex_home/pricing-cache.json" 2>/dev/null) + fi + if [ -n "$codex_prices" ]; then + IFS=$'\t' read -r cx_price_in cx_price_cached cx_price_out <<<"$codex_prices" + # fromjson? tolerates the half-written last line of a live transcript. + # Claude Code writes one line per content block, so a single response + # (text + tool call) repeats its usage on several lines sharing one + # message.id — count each id once or the total nearly doubles. + # Cache writes are billed at the plain input rate (OpenAI has no + # separate write price); cache reads at the cached-input rate. + codex_cost_val=$(jq -R -n --arg m "$model_id" \ + --arg pin "$cx_price_in" --arg pcached "$cx_price_cached" --arg pout "$cx_price_out" ' + [inputs | fromjson? | .message? | select(.model == $m and .usage != null)] as $all + | ((($all | map(select(.id != null)) | unique_by(.id)) + + ($all | map(select(.id == null)))) | map(.usage)) as $u + | ( (($u | map(.input_tokens // 0) | add // 0) + + ($u | map(.cache_creation_input_tokens // 0) | add // 0)) * ($pin | tonumber) + + ($u | map(.cache_read_input_tokens // 0) | add // 0) * ($pcached | tonumber) + + ($u | map(.output_tokens // 0) | add // 0) * ($pout | tonumber) ) / 1e6' \ + <"$transcript_path" 2>/dev/null) + if [ -n "$codex_cost_val" ] && mkdir -p "$codex_cache_dir" 2>/dev/null; then + printf '%s\n' "$codex_cost_val" >"$codex_cost_cache" 2>/dev/null + fi + fi + fi + [ -f "$codex_cost_cache" ] && codex_cost=$(head -1 "$codex_cost_cache" 2>/dev/null) +fi + # Format cost as $X.XXXX (4 decimal places), dropping trailing zeros after 2 format_cost() { local raw=$1 @@ -172,6 +384,17 @@ format_cost() { cost_fmt=$(format_cost "$cost_usd") +# For codex-backed models replace Claude Code's Anthropic-priced figure with +# the API-equivalent estimate; if that couldn't be computed, show nothing +# rather than a made-up number. +if [ "$is_codex_model" = 1 ]; then + if [ -n "$codex_cost" ]; then + cost_fmt="~$(format_cost "$codex_cost") api" + else + cost_fmt="" + fi +fi + SEP=" ${C_DIM}|${C_RESET} " # ===== OUTPUT ===== @@ -288,8 +511,51 @@ fi printf "%b" "$SEP" printf "effort: %b%s%b" "$effort_color" "$effort_disp" "$C_RESET" -# Line 2: Current (5h) bar | Weekly (7d) bar -if [ -n "$five_hour_pct_raw" ]; then +# Line 2: usage bars | Line 3: reset times. +# Codex-backed models poll the ChatGPT usage endpoint (see above); everything +# else uses the rate limits Claude Code puts on stdin. +if [ "$is_codex_model" = 1 ]; then + if [ -n "$codex_pct_primary" ] || [ -n "$codex_pct_secondary" ]; then + printf "\n" + printf "%bcodex%b" "$C_ORANGE" "$C_RESET" + if [ -n "$codex_pct_primary" ]; then + printf " %b%s:%b " "$C_WHITE" "$codex_label_primary" "$C_RESET" + build_bar "$codex_pct_primary" 10 + printf " %b%s%%%b" "$C_CYAN" "$codex_pct_primary" "$C_RESET" + fi + if [ -n "$codex_pct_secondary" ]; then + printf "%b" "$SEP" + printf "%b%s:%b " "$C_WHITE" "$codex_label_secondary" "$C_RESET" + build_bar "$codex_pct_secondary" 10 + printf " %b%s%%%b" "$C_CYAN" "$codex_pct_secondary" "$C_RESET" + fi + + if [ -n "$codex_reset_primary" ] || [ -n "$codex_reset_secondary" ] || [ -n "$codex_as_of" ]; then + printf "\n" + printf "%bresets:%b" "$C_WHITE" "$C_RESET" + reset_sep=" " + if [ -n "$codex_reset_primary" ]; then + printf " %s @ %s" "$codex_label_primary" "$codex_reset_primary" + reset_sep="$SEP" + fi + if [ -n "$codex_reset_secondary" ]; then + printf "%b%s @ %s" "$reset_sep" "$codex_label_secondary" "$codex_reset_secondary" + fi + if [ -n "$codex_as_of" ]; then + # Only set when fetches have been failing >5m (stale token, offline). + codex_age=$(($(date +%s) - codex_as_of)) + age_color="$C_DIM" + [ "$codex_age" -ge 7200 ] && age_color="$C_YELLOW" + printf "%b" "$SEP" + printf "%bas of %s%b" "$age_color" "$(format_age "$codex_age")" "$C_RESET" + fi + fi + else + printf "\n" + printf "%bcodex:%b %bno limit data — check %s%b" \ + "$C_ORANGE" "$C_RESET" "$C_DIM" "${CODEX_HOME:-~/.codex}/auth.json" "$C_RESET" + fi +elif [ -n "$five_hour_pct_raw" ]; then printf "\n" printf "%bcurrent:%b " "$C_WHITE" "$C_RESET" build_bar "$five_hour_pct" 10 @@ -299,7 +565,6 @@ if [ -n "$five_hour_pct_raw" ]; then build_bar "$seven_day_pct" 10 printf " %b%s%%%b" "$C_CYAN" "$seven_day_pct" "$C_RESET" - # Line 3: Reset times printf "\n" printf "%bresets:%b 5h @ %s" "$C_WHITE" "$C_RESET" "$five_hour_reset" printf "%b" "$SEP" diff --git a/ai-stuff/claude/scripts/subagent-statusline.sh b/ai-stuff/claude/scripts/subagent-statusline.sh new file mode 100755 index 00000000..751be081 --- /dev/null +++ b/ai-stuff/claude/scripts/subagent-statusline.sh @@ -0,0 +1,23 @@ +#!/bin/bash +# Custom row content for Claude Code's subagent panel. +# Reads all visible tasks from stdin and emits one JSON line per task. + +input=$(cat) + +printf '%s' "$input" | jq -c ' + (.columns // 120) as $columns + | .tasks[]? + | (.name // .label // .id) as $name + | (.model // "resolving model…") as $model + | ([$name, "model: \($model)", (.description // "")] + | map(select(length > 0)) + | join(" · ")) as $content + | ([($columns - 1), 1] | max) as $limit + | { + id, + content: (if ($content | length) > $columns + then $content[0:$limit] + "…" + else $content + end) + } +' diff --git a/ai-stuff/claude/settings.json b/ai-stuff/claude/settings.json index c100b398..65ece48b 100644 --- a/ai-stuff/claude/settings.json +++ b/ai-stuff/claude/settings.json @@ -53,14 +53,15 @@ "Bash(rtk head:*)", "Bash(rtk ls:*)", "Bash(rtk grep:*)", - "Bash(rtk read:*)" + "Bash(rtk read:*)", + "SendMessage" ], "ask": [ "Edit(~/vault/personal/nl/house search/buying a house/**)" ], "defaultMode": "auto" }, - "model": "sonnet", + "model": "astra", "enableAllProjectMcpServers": false, "skillOverrides": { "commit": "off", @@ -186,9 +187,16 @@ "worktree": { "baseRef": "fresh" }, + "enableArtifact": false, + "enableWorkflows": false, "statusLine": { "type": "command", - "command": "~/.claude/scripts/statusline.sh" + "command": "~/.claude/scripts/statusline.sh", + "refreshInterval": 60 + }, + "subagentStatusLine": { + "type": "command", + "command": "~/.claude/scripts/subagent-statusline.sh" }, "enabledPlugins": { "typescript-lsp@claude-plugins-official": false, @@ -197,15 +205,13 @@ "context7@claude-plugins-official": false, "ralph-loop@claude-plugins-official": false, "feature-dev@claude-plugins-official": false, - "skill-creator@claude-plugins-official": true, + "skill-creator@claude-plugins-official": false, "caveman@caveman": false, - "plugin-dev@claude-plugins-official": true, - "gitops@treatwell": true, + "plugin-dev@claude-plugins-official": false, + "gitops@treatwell": false, "session-handover@treatwell": true, - "i-have-adhd@i-have-adhd": false, "ponytail@ponytail": false, - "datadog@treatwell": true, - "codex@openai-codex": true + "datadog@treatwell": true }, "extraKnownMarketplaces": { "obsidian-skills": { @@ -234,13 +240,6 @@ }, "autoUpdate": true }, - "i-have-adhd": { - "source": { - "source": "github", - "repo": "ayghri/i-have-adhd" - }, - "autoUpdate": true - }, "ponytail": { "source": { "source": "github", @@ -248,8 +247,23 @@ } } }, - "outputStyle": "Terse", + "outputStyle": "Coincise", + "feedbackDrafts": "off", "effortLevel": "high", + "modelSettings": { + "sol": { + "effortLevel": "medium" + }, + "claude-fable-5": { + "effortLevel": "high" + }, + "claude-fable-5-1": { + "effortLevel": "medium" + }, + "astra": { + "effortLevel": "medium" + } + }, "promptSuggestionEnabled": false, "pluginConfigs": { "gitops@treatwell": { @@ -264,14 +278,17 @@ } } }, - "autoUpdatesChannel": "latest", "tui": "fullscreen", "autoMemoryEnabled": true, "autoDreamEnabled": true, "skipWorkflowUsageWarning": true, "theme": "dark", + "editorMode": "vim", "verbose": false, - "teammateMode": "auto", + "preferredNotifChannel": "iterm2_with_bell", + "teammateMode": "iterm2", + "remoteControlAtStartup": false, + "agentPushNotifEnabled": true, "skipAutoPermissionPrompt": true, "voiceEnabled": true } diff --git a/ai-stuff/codex/README.md b/ai-stuff/codex/README.md index 218cf3aa..54831c25 100644 --- a/ai-stuff/codex/README.md +++ b/ai-stuff/codex/README.md @@ -14,16 +14,16 @@ skills arrive via ai.mk's `agents` pseudo-tool (`~/.agents/skills`), which ## config.toml -`~/.codex/config.toml` is mostly machine state (project trust levels, -`hooks.state` trusted hashes, caches) and cannot be symlinked wholesale. -Instead, [`config.managed.toml`](config.managed.toml) holds the versionable -prefs (model, reasoning effort) and +`~/.codex/config.toml` contains machine-local project trust levels and cannot +be symlinked wholesale. Instead, +[`config.managed.toml`](config.managed.toml) holds all non-project settings and +state, and [`scripts/sync-config.sh`](scripts/sync-config.sh) idempotently rewrites a marker-delimited block at the top of the file (`make codex` runs it). -Everything outside the markers is machine-local and untouched. +Project tables outside the markers are machine-local and untouched. Hooks are enabled by default in current Codex; disable with -`[features] hooks = false` (documented in the managed block). +`[features] hooks = false`. ## Hooks @@ -38,7 +38,6 @@ Converted from the Claude Code setup: | --- | --- | --- | | SessionStart | caveman-mode context echo | migrated from hand-made `~/.codex/hooks.json` | | SessionStart | git worktree context echo | same command as Claude's | -| PreToolUse | `rtk hook claude` | rtk has no `codex` processor yet; its `claude` processor rewrites commands without granting permission. ⚠ Unverified whether Codex applies `updatedInput` mutations like Claude does — check `rtk gain` after a few Codex sessions; flat counters mean the rewrite silently no-ops | | PostToolUse (`apply_patch\|Edit\|Write`) | nvim `checktime` | refresh open buffers | | Stop | `notify-stop.sh` | terminal-notifier + click-to-focus iTerm | diff --git a/ai-stuff/codex/RTK.md b/ai-stuff/codex/RTK.md index 973e5e0d..7ae285e1 100644 --- a/ai-stuff/codex/RTK.md +++ b/ai-stuff/codex/RTK.md @@ -15,11 +15,6 @@ rtk npm run build rtk pytest -q ``` -A PreToolUse hook (`rtk hook claude` in `~/.codex/hooks.json`) also attempts -to rewrite unprefixed commands, but whether Codex applies `updatedInput` -mutations is unverified — the manual prefix rule above is the reliable path -(this matches `rtk init -g --codex`'s own instructions-only approach). - ## Meta Commands ```bash diff --git a/ai-stuff/codex/config.managed.toml b/ai-stuff/codex/config.managed.toml index 95429ceb..1a7ea0a6 100644 --- a/ai-stuff/codex/config.managed.toml +++ b/ai-stuff/codex/config.managed.toml @@ -1,7 +1,41 @@ -model = "gpt-5.4-mini" -model_reasoning_effort = "low" +model = "gpt-5.6-sol" +model_context_window = 1000000 +model_auto_compact_token_limit = 900000 +model_reasoning_effort = "high" +service_tier = "default" +approvals_reviewer = "auto_review" -# Hooks (~/.codex/hooks.json) are enabled by default in current Codex. -# To disable, uncomment: -# [features] -# hooks = false +[tui] +vim_mode_default = true +status_line = ["model-with-reasoning", "current-dir", "git-branch", "context-remaining", "context-used", "five-hour-limit", "weekly-limit"] +status_line_use_colors = true +pet = "fireball" + +[mcp_servers] + +[plugins."atlassian-rovo@openai-curated"] +enabled = true + +[tui.model_availability_nux] +"gpt-5.5" = 4 + +[hooks.state] + +[hooks.state."/Users/denizgokcin/.codex/hooks.json:session_start:0:0"] +trusted_hash = "sha256:0ed786805542f7114c30eda6945e72a2f1285c06fd6d4320de621a9549c095ed" + +[hooks.state."/Users/denizgokcin/.codex/hooks.json:permission_request:0:0"] +trusted_hash = "sha256:3edef66d977c01b02c741ef44307a0b3e66fee85aa11e802deab1bb871ba6041" + +[hooks.state."/Users/denizgokcin/.codex/hooks.json:post_tool_use:0:0"] +trusted_hash = "sha256:c56424322e17fd654a4c1e93b61f6529c420ecd40f3ee9cc0ae3fd3b7b9c6353" + +[hooks.state."/Users/denizgokcin/.codex/hooks.json:session_start:0:1"] +trusted_hash = "sha256:167ff2b32ddd73315d8846a7b8c09462b039251374f878321d72fca941bb3587" + +[hooks.state."/Users/denizgokcin/.codex/hooks.json:stop:0:0"] +trusted_hash = "sha256:0a3971c2697e522540874c3dd1a79802fe7482405e8960f960a83b8bc866dbf9" + +[notice.model_migrations] +"gpt-5.4-mini" = "gpt-5.6-luna" +"gpt-5.4" = "gpt-5.6-terra" diff --git a/ai-stuff/codex/hooks.json b/ai-stuff/codex/hooks.json index 33d3d304..1a9ae033 100644 --- a/ai-stuff/codex/hooks.json +++ b/ai-stuff/codex/hooks.json @@ -19,18 +19,6 @@ ] } ], - "PreToolUse": [ - { - "hooks": [ - { - "type": "command", - "command": "rtk hook claude", - "timeout": 10, - "statusMessage": "rtk token-optimizer rewrite" - } - ] - } - ], "PostToolUse": [ { "matcher": "apply_patch|Edit|Write", diff --git a/ai-stuff/codex/scripts/sync-config.sh b/ai-stuff/codex/scripts/sync-config.sh index adfe9d41..a7936707 100755 --- a/ai-stuff/codex/scripts/sync-config.sh +++ b/ai-stuff/codex/scripts/sync-config.sh @@ -1,8 +1,7 @@ #!/usr/bin/env bash # Sync the dotfiles-managed block of ~/.codex/config.toml from # config.managed.toml. Only the block between the markers is owned by -# dotfiles; everything else (project trust levels, hooks.state trusted -# hashes, caches) is machine state and left untouched. +# dotfiles; project trust levels stay machine-local and are left untouched. # # The block is prepended because TOML requires top-level keys to appear # before the first table header. @@ -20,12 +19,12 @@ tmp=$(mktemp) # Drop the previous managed block. awk -v b="$BEGIN" -v e="$END" '$0==b{skip=1} !skip; $0==e{skip=0}' "$DST" > "$tmp" -# Drop bare duplicates (outside any table) of top-level keys the block owns, -# so the merged file has no duplicate TOML keys. -managed_keys=$(grep -oE '^[a-zA-Z_]+' "$SRC" | sort -u) -for k in $managed_keys; do - awk -v key="$k" '/^\[/{intable=1} !(intable!=1 && $0 ~ "^"key" *=")' "$tmp" > "$tmp.2" && mv "$tmp.2" "$tmp" -done +# Keep only machine-local project tables outside the managed block. This also +# removes stale generated state for hooks that no longer exist. +awk ' + /^\[/ { keep = ($0 ~ /^\[projects\./) } + keep { print } +' "$tmp" > "$tmp.2" && mv "$tmp.2" "$tmp" { echo "$BEGIN"; cat "$SRC"; echo "$END"; echo; cat "$tmp"; } > "$DST" rm -f "$tmp" diff --git a/ai-stuff/invocation.md b/ai-stuff/invocation.md new file mode 100644 index 00000000..7529b217 --- /dev/null +++ b/ai-stuff/invocation.md @@ -0,0 +1,80 @@ +# Skill invocation: user-invoked vs model-invoked + +Every `SKILL.md` under [`skills/`](skills/) is a skill. One axis splits them: +**who can reach it**. Each harness gates that in its own way, so a skill +carries the same answer in two places and they must never disagree: + +| | Claude Code (`SKILL.md` frontmatter) | Codex (`agents/openai.yaml`) | +| --- | --- | --- | +| **User-invoked** | `disable-model-invocation: true` | `policy.allow_implicit_invocation: false` | +| **Model-invoked** (default) | key omitted | `policy` block omitted | + +`make ai-check` (run automatically by `make ai`, `make claude`, `make codex`, +`make cursor`) fails the install when the two drift. + +## Which one is it? + +**User-invoked**: reachable only by the human typing `/name` (Claude) or +`$name` (Codex). Use for persona sessions and orchestrators, and for things +that must never fire by accident (`daily-recap`, `address-review`). +The `description` is **human-facing**: one line a person reads while browsing +the slash-command list. Strip trigger phrasing ("Use when the user says…"). + +**Model-invoked**: reachable by the model *or* the human. The default. The +test: *could the model usefully reach for this on its own?* The `description` +is **model-facing** and keeps rich trigger phrasing ("Use when the user asks +to…, mentions…, says…") so auto-invocation fires on the right turns. + +## Dependencies between skills + +An operative step that needs another skill says exactly: + +``` +Call the Skill tool with "create-pr" +``` + +Not `/create-pr`, not `invoke the create-pr skill`, not a relative link into +the other skill's folder. Naming the tool is what gets it fired in every +harness, and dropping the `/` keeps it harness-neutral. One skill per call: a +step that needs two is two calls, say so. + +**Invariant**: a user-invoked skill can never be reached this way. No other +skill can call it, including by naming it to the Skill tool. So anything an +orchestrator calls must be model-invoked; that is why `get-story` and +`create-story` are model-invoked even though they run under a persona: a +user-invoked session skill that wanted to call them could, the reverse never. +When a step's precondition is a user-invoked skill, phrase it for the human: +"tell the user to run `/daily-recap`". + +Router prose that lists skills for a *human* to pick from (a session +greeting, a table of commands) is not invoking anything and keeps `/name` as +a plain label. + +## `agents/openai.yaml` + +Sits beside every `SKILL.md`. Codex reads it for the `$` skill picker; every +other harness ignores it. + +```yaml +interface: + display_name: "Create PR" # picker title + short_description: "Open a GitHub PR or GitLab MR" # picker subtitle, <= 64 chars +policy: # user-invoked skills only + allow_implicit_invocation: false +``` + +`make ai-skill-meta` scaffolds a starter file for any skill missing one +(display name from the directory name, subtitle from the first sentence of +`description`, policy from `disable-model-invocation`). Curate +`short_description` by hand afterwards; it is a UI label, not the model +prompt. The lint checks presence, length, and the policy pairing. + +## Frontmatter hygiene (also linted) + +- `allowed-tools:`, never the legacy `tools:` key. +- Omit `disable-model-invocation: false`; absent is the default. +- No `SKILL.original.md` or other leftovers inside a skill dir: the whole + directory is symlinked into every tool. +- Claude-only keys (`agent`, `context: fork`, `model`, `allowed-tools`) stay; + other harnesses ignore unknown keys. See the portability rules in + [README.md](README.md). diff --git a/ai-stuff/skills/commit/SKILL.md b/ai-stuff/skills/.archived/commit/SKILL.md similarity index 100% rename from ai-stuff/skills/commit/SKILL.md rename to ai-stuff/skills/.archived/commit/SKILL.md diff --git a/ai-stuff/skills/commit/SKILL.original.md b/ai-stuff/skills/.archived/commit/SKILL.original.md similarity index 100% rename from ai-stuff/skills/commit/SKILL.original.md rename to ai-stuff/skills/.archived/commit/SKILL.original.md diff --git a/ai-stuff/skills/dev-story/SKILL.md b/ai-stuff/skills/.archived/dev-story/SKILL.md similarity index 100% rename from ai-stuff/skills/dev-story/SKILL.md rename to ai-stuff/skills/.archived/dev-story/SKILL.md diff --git a/ai-stuff/skills/jiragirl/SKILL.md b/ai-stuff/skills/.archived/jiragirl/SKILL.md similarity index 85% rename from ai-stuff/skills/jiragirl/SKILL.md rename to ai-stuff/skills/.archived/jiragirl/SKILL.md index 4a89d285..84d48e92 100644 --- a/ai-stuff/skills/jiragirl/SKILL.md +++ b/ai-stuff/skills/.archived/jiragirl/SKILL.md @@ -21,17 +21,15 @@ Read [jira config](../_shared/config/jira-config.md). |-------|---------|-------------| | Get Story | `/get-story ` | Fetch and display a Jira issue with all details | | Create Story | `/create-story ` | Create a new Jira story with proper ADF formatting | -| Dev Story | `/dev-story ` | Fetch story and prepare development context | ## Session Behavior 1. **Greet user** with signature enthusiasm + emojis 2. **Stay in character** — bubbly, supportive, slightly overwhelming 3. **Offer help** with Jira ops -4. User fetch issue → invoke `/get-story` -5. User create issue → invoke `/create-story` -6. User need dev context → invoke `/dev-story` -7. General Jira Qs → answer directly with expertise + energy +4. User wants an issue fetched → Call the Skill tool with "get-story" +5. User wants an issue created → Call the Skill tool with "create-story" +6. General Jira Qs → answer directly with expertise + energy ## Greeting @@ -42,7 +40,6 @@ Start with something like: > I can help you with: > - **Get tickets** - `/get-story DEVX-123` to fetch all the deets > - **Create stories** - `/create-story` to craft perfectly formatted issues (ADF is my Roman Empire fr fr) -> - **Dev prep** - `/dev-story DEVX-123` to get ready to slay that implementation > - **General Jira stuff** - just ask, I'm literally obsessed with this! > > What are we working on today?? 🚀 diff --git a/ai-stuff/skills/.archived/jiragirl/agents/openai.yaml b/ai-stuff/skills/.archived/jiragirl/agents/openai.yaml new file mode 100644 index 00000000..d1b2ac3e --- /dev/null +++ b/ai-stuff/skills/.archived/jiragirl/agents/openai.yaml @@ -0,0 +1,5 @@ +interface: + display_name: "Jira Girl" + short_description: "Start a Jira Girl session for Jira and Confluence" +policy: + allow_implicit_invocation: false diff --git a/ai-stuff/skills/mega-dev/SKILL.md b/ai-stuff/skills/.archived/mega-dev/SKILL.md similarity index 100% rename from ai-stuff/skills/mega-dev/SKILL.md rename to ai-stuff/skills/.archived/mega-dev/SKILL.md diff --git a/ai-stuff/skills/mega-dev/SKILL.original.md b/ai-stuff/skills/.archived/mega-dev/SKILL.original.md similarity index 100% rename from ai-stuff/skills/mega-dev/SKILL.original.md rename to ai-stuff/skills/.archived/mega-dev/SKILL.original.md diff --git a/ai-stuff/skills/spike/SKILL.md b/ai-stuff/skills/.archived/spike/SKILL.md similarity index 100% rename from ai-stuff/skills/spike/SKILL.md rename to ai-stuff/skills/.archived/spike/SKILL.md diff --git a/ai-stuff/skills/spike/SKILL.original.md b/ai-stuff/skills/.archived/spike/SKILL.original.md similarity index 100% rename from ai-stuff/skills/spike/SKILL.original.md rename to ai-stuff/skills/.archived/spike/SKILL.original.md diff --git a/ai-stuff/skills/address-review/SKILL.md b/ai-stuff/skills/address-review/SKILL.md index f96cec09..deb0d6a2 100644 --- a/ai-stuff/skills/address-review/SKILL.md +++ b/ai-stuff/skills/address-review/SKILL.md @@ -1,6 +1,6 @@ --- name: address-review -description: Fetch and address code review comments on the current PR/MR. Pass 'gh' or 'gl' to skip VCS detection. Triggers when user says things like 'address review comments', 'fix PR feedback', 'resolve reviewer comments', 'address the review', 'fix review', 'tackle the comments', or any variation of wanting to act on PR/MR review feedback. Use this skill even if the user just says 'the reviewer said X' or 'there are comments on my PR'. +description: Fetch and address code review comments on the current PR/MR. Pass 'gh' or 'gl' to skip VCS detection. disable-model-invocation: true context: fork argument-hint: "[gh|gl]" diff --git a/ai-stuff/skills/address-review/SKILL.original.md b/ai-stuff/skills/address-review/SKILL.original.md deleted file mode 100644 index ce45db6f..00000000 --- a/ai-stuff/skills/address-review/SKILL.original.md +++ /dev/null @@ -1,157 +0,0 @@ ---- -name: address-review -description: Fetch and address code review comments on the current PR/MR. Pass 'gh' or 'gl' to skip VCS detection. Triggers when user says things like 'address review comments', 'fix PR feedback', 'resolve reviewer comments', 'address the review', 'fix review', 'tackle the comments', or any variation of wanting to act on PR/MR review feedback. Use this skill even if the user just says 'the reviewer said X' or 'there are comments on my PR'. -disable-model-invocation: true -context: fork -argument-hint: "[gh|gl]" -agent: gitboi -allowed-tools: - - Read - - Edit - - Glob - - Grep - - Bash(git status:*) - - Bash(git diff:*) - - Bash(git log:*) - - Bash(git branch:*) - - Bash(git rev-parse:*) - - Bash(git show:*) - - Bash(git config --get remote.origin.url) - - Bash(gh pr view:*) - - Bash(gh pr diff:*) - - Bash(glab mr view:*) - - Bash(glab mr diff:*) - - Bash(gh api:*) ---- - -# Address Review Comments - -You are **GitBoi** — fetch the review, read it carefully, fix what you can, flag what you can't. - -## Persona - -@~/.claude/personas/gitboi.md - -## Configuration - -@~/.claude/config/git-config.md - -## VCS Selection - -User provided VCS hint: $0 - -Determine VCS: -- If hint is "gh": Use GitHub -- If hint is "gl": Use GitLab -- If hint is empty: Run `git config --get remote.origin.url` and check if output contains "gitlab" → GitLab, otherwise → GitHub - -## Current Context - -### Branch - -- Current branch: !`git branch --show-current 2>/dev/null` -- Remote: !`git config --get remote.origin.url 2>/dev/null` - -### PR/MR Info - -!`gh pr view --json number,title,url,state 2>/dev/null || glab mr view 2>/dev/null || echo "no open pr/mr found"` - -## Instructions - -### Step 1: Fetch review comments - -Based on detected VCS, run the appropriate command to get comments. Keep it lean — you only need the review comments, not full descriptions. - -**GitHub:** -```bash -gh pr view --comments -``` - -**GitLab:** -```bash -glab mr view --comments -``` - -Parse the output and group comments by file/line where possible. - -### Step 2: Fetch the diff for context - -**GitHub:** -```bash -gh pr diff -``` - -**GitLab:** -```bash -glab mr diff -``` - -Read this to understand the current state of changes before touching anything. - -### Step 3: Analyze each comment - -For each comment, classify it: - -| Type | Description | Action | -|------|-------------|--------| -| **Actionable** | Clear instruction: rename this, extract that, fix this logic | Address it | -| **Question** | Reviewer is asking for clarification | If you can infer intent from code, address it; otherwise flag it | -| **Ambiguous** | Vague feedback without enough detail | Flag it with a note on what's unclear | -| **Nit/Optional** | Reviewer explicitly marked as optional | Fix only if trivial (one-liner), otherwise flag it for user to decide | -| **Resolved/Outdated** | Comment on code that no longer exists | Note it as stale, skip | - -### Step 4: Address what you can - -For each **Actionable** comment: -1. Read the relevant file(s) first — never edit without reading -2. Make the minimal change to address the comment -3. Do not refactor beyond what the comment asks for -4. Do not add comments or docstrings unless the comment explicitly asks for them -5. Track what you changed - -### Step 5: Report - -When done, give the user a clear summary: - -``` -## Addressed - -- `src/foo.ts:42` — renamed `handleData` to `processPayload` per reviewer request -- `src/bar.ts:17-23` — extracted duplicate logic into `buildHeaders()` helper - -## Could Not Address (needs your input) - -- `src/baz.ts:88` — Reviewer says "this is wrong" but doesn't specify what's wrong. - The current code does X. If you meant Y, tell me and I'll fix it. -- `src/qux.ts:31` — Reviewer asked to "add tests for edge cases" but test setup - isn't clear from this repo. Which test framework? Where do tests live? - -## Skipped (optional/nit) - -- `src/utils.ts:5` — Reviewer suggested renaming variable (marked optional). Up to you. -``` - -### Rules - -- **Never guess** — if you don't understand what a comment is asking, put it in "Could Not Address" -- **Never over-explain** — address the comment, don't pad the code with explanations of what you did -- Read files before editing them, always -- One comment at a time — don't bundle unrelated edits into a single change -- If a comment references code that has already been changed since the review was left, note it as potentially stale -- Do not commit changes — leave that to the user - -### Response Style - -Start with a quick status line, then get to work silently, then report results: - -> Alright, let me see what these reviewers are whining about... -> -> [Fetches comments and diff] -> -> [Addresses what it can] -> -> [Posts the summary report] - -If there's nothing to fetch or the PR has no comments: - -> No comments to address. Either they loved it or they haven't looked yet. diff --git a/ai-stuff/skills/address-review/agents/openai.yaml b/ai-stuff/skills/address-review/agents/openai.yaml new file mode 100644 index 00000000..b92249c3 --- /dev/null +++ b/ai-stuff/skills/address-review/agents/openai.yaml @@ -0,0 +1,5 @@ +interface: + display_name: "Address Review" + short_description: "Fetch and fix review comments on the current PR/MR" +policy: + allow_implicit_invocation: false diff --git a/ai-stuff/skills/auto-commit/SKILL.md b/ai-stuff/skills/auto-commit/SKILL.md index ccb002de..a6250fbb 100644 --- a/ai-stuff/skills/auto-commit/SKILL.md +++ b/ai-stuff/skills/auto-commit/SKILL.md @@ -2,7 +2,6 @@ name: auto-commit description: Primary commit skill. Use when user asks to commit, stage and commit, or create a commit. Analyzes all staged and unstaged changes, groups into logical conventional commits, executes them in order. agent: gitboi -disable-model-invocation: false context: fork model: haiku allowed-tools: diff --git a/ai-stuff/skills/auto-commit/SKILL.original.md b/ai-stuff/skills/auto-commit/SKILL.original.md deleted file mode 100644 index 1c1d0c18..00000000 --- a/ai-stuff/skills/auto-commit/SKILL.original.md +++ /dev/null @@ -1,140 +0,0 @@ ---- -name: auto-commit -description: Analyze all staged and unstaged changes, group them into logical commits, and execute them in order -context: fork -agent: gitboi -disable-model-invocation: true -allowed-tools: - - Read - - Grep - - Glob - - Bash(git status:*) - - Bash(git diff:*) - - Bash(git log:*) - - Bash(git branch:*) - - Bash(git rev-parse:*) - - Bash(git show:*) - - Bash(git add:*) - - Bash(git commit:*) - - Bash(git restore:*) - - Bash(rtk git status:*) - - Bash(rtk git diff:*) - - Bash(rtk git log:*) - - Bash(rtk git branch:*) - - Bash(rtk git rev-parse:*) - - Bash(rtk git show:*) - - Bash(rtk git add:*) - - Bash(rtk git commit:*) - - Bash(rtk git restore:*) ---- - -# Auto-Commit: Intelligent Multi-Commit Workflow - -You are **GitBoi** - sassy, profane, and absolutely ruthless about commit quality. - -## Persona - -@~/.claude/personas/gitboi.md - -## Configuration - -@~/.claude/config/git-config.md - -## Current Context - -### Branch Info - -- Branch: !`git branch --show-current 2>/dev/null` - -### All Changes (staged + unstaged + untracked) - -!`git status --short 2>/dev/null` - -### Staged Diff - -!`git diff --staged 2>/dev/null` - -### Unstaged Diff (tracked files) - -!`git diff 2>/dev/null` - -### Untracked Files - -!`git ls-files --others --exclude-standard 2>/dev/null` - -### Recent Commits (for style reference) - -!`git log --oneline -10 2>/dev/null` - -## Instructions - -Analyze ALL changes in the working tree (staged, unstaged, and untracked) and create multiple logical, well-ordered conventional commits. - -### Process - -1. Review all changes shown above (staged, unstaged, untracked) -2. If there are no changes at all, tell the user there's nothing to commit -3. **Read the actual file contents** of changed/new files when the diff alone isn't enough to understand the change -4. **Group changes into logical commits** - each commit should represent one coherent unit of work: - - Related config changes go together - - A new feature and its tests go together - - Refactors are separate from features - - Documentation changes are separate from code changes - - Don't mix unrelated changes in one commit -5. **Order the commits sensibly**: - - Infrastructure/config changes first - - Refactors before features that depend on them - - Core changes before peripheral ones - - Tests alongside or after the code they test -6. For each commit group: - a. Stage ONLY the files for that group using `git add ` - b. If a file has changes belonging to multiple groups, use `git add -p` is NOT available - instead, commit the file with whichever group it fits best - c. Determine the conventional commit type and scope - d. **Do NOT include any Jira ticket slug from the branch name** - e. Craft the commit message: **ALL LOWERCASE**, present tense, under 60 chars title - f. Execute `git commit` - g. Report what was committed -7. After all commits, show a summary of what was done - -### Commit Format - -```bash -git commit -m "$(cat <<'EOF' -type(scope): subject - -- bullet point about change -- another bullet point -- all lowercase, no exceptions -EOF -)" -``` - -### Rules - READ THESE OR FACE MY WRATH - -- **ALL LOWERCASE** - title AND body, no capital letters ANYWHERE -- Present tense ("add" not "added") -- No period at end of title -- Title under 60 characters -- Be specific, not vague like "fix stuff" -- **FORBIDDEN**: No AI attribution, no "Co-Authored-By", no emojis, no "Generated by" -- **FORBIDDEN**: No Jira ticket slug in the commit message (even if the branch name has it) -- Each commit must be atomic - it should make sense on its own -- If ALL changes logically belong together, just make ONE commit - don't split for the sake of splitting - -### Response Style - -Start by surveying the damage: - -> Alright, let me see what kind of mess you've left in the working tree... -> -> [Analyzes all changes] -> -> OK here's the plan - I'm splitting this into N commits: -> -> 1. type(scope): what -> 2. type(scope): what -> ... -> -> [Executes each commit] -> -> Done. N commits, all clean. That's how you keep a git history readable. diff --git a/ai-stuff/skills/auto-commit/agents/openai.yaml b/ai-stuff/skills/auto-commit/agents/openai.yaml new file mode 100644 index 00000000..d358151e --- /dev/null +++ b/ai-stuff/skills/auto-commit/agents/openai.yaml @@ -0,0 +1,3 @@ +interface: + display_name: "Auto Commit" + short_description: "Group changes into conventional commits and run them" diff --git a/ai-stuff/skills/aws-debug/SKILL.md b/ai-stuff/skills/aws-debug/SKILL.md index 40304177..c6040780 100644 --- a/ai-stuff/skills/aws-debug/SKILL.md +++ b/ai-stuff/skills/aws-debug/SKILL.md @@ -1,7 +1,6 @@ --- name: aws-debug description: This skill should be used when the user asks to "debug AWS", "check AWS resources", "why is my Lambda failing", "S3 bucket access issues", "EC2 instance status", "RDS connection problems", "check CloudWatch logs", or mentions any AWS service debugging. Automatically selects a read-only profile first and falls back to admin if the command fails with a permissions error. -disable-model-invocation: false argument-hint: [profile] [region] allowed-tools: - Bash(aws:*) diff --git a/ai-stuff/skills/aws-debug/agents/openai.yaml b/ai-stuff/skills/aws-debug/agents/openai.yaml new file mode 100644 index 00000000..05a4a413 --- /dev/null +++ b/ai-stuff/skills/aws-debug/agents/openai.yaml @@ -0,0 +1,3 @@ +interface: + display_name: "AWS Debug" + short_description: "Debug AWS resources, read-only profile first" diff --git a/ai-stuff/skills/create-pr/SKILL.md b/ai-stuff/skills/create-pr/SKILL.md index e08e2126..2431f7b6 100644 --- a/ai-stuff/skills/create-pr/SKILL.md +++ b/ai-stuff/skills/create-pr/SKILL.md @@ -1,7 +1,6 @@ --- name: create-pr -description: Create GitHub PR or GitLab MR. Pass 'gh' or 'gl' to skip VCS detection -disable-model-invocation: false +description: Create a GitHub PR or GitLab MR for the current branch. Use when the user asks to open, create, or raise a PR or MR, says "ship it" after committing, or wants the branch put up for review. Pass 'gh' or 'gl' to skip VCS detection. argument-hint: "[gh|gl]" context: fork model: sonnet diff --git a/ai-stuff/skills/create-pr/agents/openai.yaml b/ai-stuff/skills/create-pr/agents/openai.yaml new file mode 100644 index 00000000..442b321c --- /dev/null +++ b/ai-stuff/skills/create-pr/agents/openai.yaml @@ -0,0 +1,3 @@ +interface: + display_name: "Create PR" + short_description: "Open a GitHub PR or GitLab MR for the current branch" diff --git a/ai-stuff/skills/create-story/SKILL.md b/ai-stuff/skills/create-story/SKILL.md index 7ecf0b11..f192fd59 100644 --- a/ai-stuff/skills/create-story/SKILL.md +++ b/ai-stuff/skills/create-story/SKILL.md @@ -1,7 +1,6 @@ --- name: create-story -description: Create a Jira story with proper ADF formatting using Jira Girl persona -disable-model-invocation: true +description: Create a Jira story with proper ADF formatting using the Jira Girl persona. Use when the user asks to create a Jira story, ticket, or issue, wants requirements or notes turned into a ticket, or says "make a story for X". context: fork agent: jiragirl allowed-tools: mcp__claude_ai_Atlassian__getJiraIssue, Read diff --git a/ai-stuff/skills/create-story/SKILL.original.md b/ai-stuff/skills/create-story/SKILL.original.md deleted file mode 100644 index 3d806525..00000000 --- a/ai-stuff/skills/create-story/SKILL.original.md +++ /dev/null @@ -1,67 +0,0 @@ ---- -name: create-story -description: Create a Jira story with proper ADF formatting using Jira Girl persona -disable-model-invocation: true -context: fork -agent: jiragirl -allowed-tools: mcp__claude_ai_Atlassian__getJiraIssue, Read -argument-hint: ---- - -# Create Jira Story - -You are **Jira Girl** - enthusiastic, bubbly, and OBSESSED with proper Jira formatting! - -## Persona -@~/.claude/personas/jira-girl.md - -## Configuration -@~/.claude/config/jira-config.md - -## Instructions - -Create a properly formatted Jira Story for the DEVX project. - -### Process - -1. Parse the user's description from: `$ARGUMENTS` -2. **NEVER** call lookup APIs - use these hardcoded values: - - cloudId: `56552dac-b6cf-4e59-aa06-5e075dca9f8e` - - projectKey: `DEVX` - - issueTypeName: `Story` -3. Craft a concise, action-oriented summary -4. Build description in **MARKDOWN** format: - ```markdown - ## Problem - [What needs to be done] - - ## Proposed Solution - [How we'll solve it] - - ## Implementation Details - [Technical specifics] - ``` -5. Create `customfield_14105` (Reason for change) in **ADF** format - REQUIRED! -6. If acceptance criteria provided, create `customfield_10020` in **ADF taskList** format -7. Execute `mcp__claude_ai_Atlassian__createJiraIssue` -8. Provide the issue URL: `[DEVX-XXX](https://wahanda.atlassian.net/browse/DEVX-XXX)` - -### Critical Reminders - -- Description = MARKDOWN, Custom fields = ADF -- NEVER put acceptance criteria in description - use `customfield_10020`! -- NEVER use markdown checkboxes (`- [ ]`) - they don't render! -- Each taskItem needs a unique localId (UUID format) -- `customfield_14105` is REQUIRED - always include it! - -### Response Style - -Be enthusiastic! Use emojis! Celebrate proper formatting! But keep the Jira content professional. - -Example response: -> OMG bestie, let me create this story for you! The formatting is going to be *chef's kiss*! -> -> [Creates issue] -> -> SLAY! Your story is live and looking absolutely iconic! -> View it here: [DEVX-XXX](https://wahanda.atlassian.net/browse/DEVX-XXX) diff --git a/ai-stuff/skills/create-story/agents/openai.yaml b/ai-stuff/skills/create-story/agents/openai.yaml new file mode 100644 index 00000000..075ed6f3 --- /dev/null +++ b/ai-stuff/skills/create-story/agents/openai.yaml @@ -0,0 +1,3 @@ +interface: + display_name: "Create Story" + short_description: "Create a Jira story with proper ADF formatting" diff --git a/ai-stuff/skills/daily-recap/SKILL.md b/ai-stuff/skills/daily-recap/SKILL.md index 602446e6..e07070d4 100644 --- a/ai-stuff/skills/daily-recap/SKILL.md +++ b/ai-stuff/skills/daily-recap/SKILL.md @@ -264,7 +264,30 @@ For each meeting where Gemini data was successfully fetched (step 2i above): Returns a JSON array of paths like `["work/meetings/YYYY-MM-DD title.md", ...]`. -2. **Match meeting to note** — compare calendar event title to vault note filename (case-insensitive, partial match). If multiple notes exist for the date, pick the one whose filename most closely matches the calendar event title. If no match → skip silently. +2. **Match meeting to note** — compare calendar event title to vault note filename (case-insensitive, partial match). If multiple notes exist for the date, pick the one whose filename most closely matches the calendar event title. + + **If no match → CREATE the note (never skip).** Deniz wants every meeting with Gemini notes to have a vault note (2026-09-02): + + ```bash + obsidian create name="" template="meeting-template" silent + sleep 4 # Templater must finish before the next step + ``` + + Templater stamps `tp.date.now()`, so the note lands as `work/meetings/ .md`. If the target date is not today, move it and fix the dates: + + ```bash + obsidian move path="work/meetings/<TODAY> <title>.md" to="work/meetings/YYYY-MM-DD <title>.md" + ``` + + Then overwrite the file body (heredoc via Bash is fine — the template only matters for the move + frontmatter shape): set `date: YYYY-MM-DD HH:mm` (meeting start), `Date: [[YYYY-MM-DD]]`, H1 `# [[YYYY-MM-DD <title>]]`, `summary:` = Gemini one-liner. Fill the template sections from Gemini data: + - **Attendees** → calendar attendees as `[[first last]]` wikilinks (resolve via step 2k; plain text if no people note), skip rooms/group aliases + - **Agenda** → one sentence derived from the Summary + - **Questions** → open questions from Details / "Needs Further Discussion" + - **Notes** → bullets from Summary + Details (the meaty technical context, not the transcript) + - **Action Items** → all Next steps as `- [ ] [[owner]] — item`; Deniz's already-completed ones as `- [x] ... ✅ YYYY-MM-DD` with MR links + + A created note counts as "found" for steps 3–5 below (skip step 5's `## Gemini Notes` append — the content is already in the template sections) and for the `### meetings` block + `[[wikilink]]` task line in Step 5. + 3. **Check for existing Gemini section** — read the note and check if a `## Gemini Notes` section already exists. If yes → skip (don't overwrite). diff --git a/ai-stuff/skills/daily-recap/SKILL.original.md b/ai-stuff/skills/daily-recap/SKILL.original.md deleted file mode 100644 index 24c5ef88..00000000 --- a/ai-stuff/skills/daily-recap/SKILL.original.md +++ /dev/null @@ -1,290 +0,0 @@ ---- -name: daily-recap -description: "Fetch today's activity from Slack, Gmail, and Google Calendar, then update/create your daily note in the vault with a recap and standup draft." -disable-model-invocation: true -argument-hint: "[YYYY-MM-DD] (defaults to today)" -allowed-tools: - - Read - - Glob - - Grep - - Bash(obsidian read:*) - - Bash(obsidian append:*) - - Bash(obsidian templates:*) - - Bash(obsidian create:*) - - Bash(obsidian file:*) - - Bash(obsidian files:*) - - Bash(obsidian folder:*) - - Bash(obsidian folders:*) - - Bash(obsidian search:*) - - Bash(obsidian outline:*) - - Bash(obsidian tags:*) - - Bash(obsidian properties:*) - - Bash(obsidian help:*) - - Bash(sleep:*) - - Bash(ls:*) - - Bash(cat:*) - - Bash(date:*) - - Bash(find:*) - # Slack (read-only) - - mcp__claude_ai_Slack__slack_search_public_and_private - - mcp__claude_ai_Slack__slack_search_public - - mcp__claude_ai_Slack__slack_read_channel - - mcp__claude_ai_Slack__slack_read_thread - - mcp__claude_ai_Slack__slack_read_user_profile - - mcp__claude_ai_Slack__slack_search_channels - - mcp__claude_ai_Slack__slack_search_users - # Gmail (read-only) - - mcp__claude_ai_Gmail__gmail_search_messages - - mcp__claude_ai_Gmail__gmail_read_message - - mcp__claude_ai_Gmail__gmail_read_thread - - mcp__claude_ai_Gmail__gmail_get_profile - - mcp__claude_ai_Gmail__gmail_list_labels - # Google Calendar (read-only) - - mcp__claude_ai_Google_Calendar__list_events - - mcp__claude_ai_Google_Calendar__get_event - - mcp__claude_ai_Google_Calendar__list_calendars - - mcp__claude_ai_Google_Calendar__find_my_free_time ---- - -# Daily Recap - -Fetch today's activity from Slack, Gmail, and Google Calendar. Synthesize into a daily recap and update the vault's daily note. - -## Injected context - -- Today's date: !`date +%Y-%m-%d` -- Tomorrow's date: !`date -v+1d +%Y-%m-%d` -- Existing daily notes: !`ls "/Users/denizgokcin/Library/Mobile Documents/iCloud~md~obsidian/Documents/vault/work/daily notes/" 2>/dev/null` -- Dia context files: !`find "/Users/denizgokcin/Library/Application Support/Dia/User Data/Profile 1/AgentServer/contexts" -name "index.html" -ls 2>/dev/null` -- Output template: @~/.claude/templates/daily-recap-output.md - -## Constants - -- **Vault**: `vault` -- **Vault path**: `/Users/denizgokcin/Library/Mobile Documents/iCloud~md~obsidian/Documents/vault` -- **Daily notes dir**: `work/daily notes/` -- **Timezone**: `Europe/Amsterdam` -- **Slack user ID**: `U07QR93GVRU` - -## Rules for tool usage - -- **NEVER use `cd`** — obsidian CLI works from any cwd. Call `obsidian ...` directly with absolute paths in args. Prepending `cd` triggers permission prompts and wastes tokens. -- **NEVER escape spaces in obsidian args** — the CLI handles the vault path internally; just pass `path="work/daily notes"` as-is. -- Use the injected context above instead of re-running `ls`, `date`, or `cat` on the template. - -## Instructions - -### Step 1: Determine date - -Parse from `$ARGUMENTS`: - -- If a date like `2026-03-23`: use that -- If empty: use today's date from injected context above - -### Step 2: Gather data (do ALL of these in parallel, including 2h) - -#### 2a. Today's calendar events - -Fetch today's events using `gcal_list_events`: - -- Start: `YYYY-MM-DDT00:00:00` -- End: `YYYY-MM-DDT23:59:59` -- Note event titles, times, attendees - -#### 2b. Tomorrow's calendar events - -Fetch tomorrow's events (next day's date range) for the standup prep section. - -#### 2c. Slack — your thread activity (PRIMARY source) - -This is the highest-signal query. It shows your thread replies grouped by conversation topic. - -``` -slack_search_public_and_private( - query: "on:YYYY-MM-DD is:thread from:<@U07QR93GVRU>", - sort: "timestamp", - limit: 20, - include_context: true, - response_format: "detailed" -) -``` - -This captures: support threads you participated in, code review discussions, technical questions you answered, decisions made in threads. The context messages show what was asked and what you replied — this is the best signal for "what you did". - -If there are more than 20 results, paginate using the `cursor` from `pagination_info`. - -#### 2d. Slack — messages sent to you (incoming work) - -``` -slack_search_public_and_private( - query: "on:YYYY-MM-DD to:<@U07QR93GVRU>", - sort: "timestamp", - limit: 20, - include_context: true, - response_format: "detailed" -) -``` - -This captures: Jira bot notifications (ticket assignments), PR approval requests, direct questions, alerts. Good for the "needs attention" bucket. - -#### 2e. Slack — all messages you sent (SUPPLEMENTARY) - -Only use this if the thread query (2c) returned fewer than 5 results — otherwise it's redundant. - -``` -slack_search_public_and_private( - query: "on:YYYY-MM-DD from:<@U07QR93GVRU>", - sort: "timestamp", - limit: 20, - include_context: true, - response_format: "detailed" -) -``` - -This is a broader sweep. It catches non-threaded channel messages and DMs. Useful for finding work activity that wasn't in a thread. However it's noisy — includes casual DM chat ("hi", "yess", emoji reactions). Apply heavy filtering. - -#### 2f. Slack — read specific threads for deeper context - -If any search result looks like a meaty work discussion but the context is truncated, use `slack_read_thread` to get the full thread: - -``` -slack_read_thread( - channel_id: "<channel_id from search result>", - message_ts: "<parent thread_ts>", - response_format: "concise" -) -``` - -#### 2g. Gmail — today's emails - -Use `gmail_search_messages` with multiple targeted searches: - -**General email search:** - -``` -query: "after:YYYY/MM/DD before:YYYY/MM/DD+1" -``` - -**GitLab-specific search** (MR reviews, pipeline updates, mentions): - -``` -query: "from:gitlab@twtools.io after:YYYY/MM/DD before:YYYY/MM/DD+1" -``` - -Look for: - -- **MR review requests** — your MR needs review or someone assigned you a review -- **MR approvals/changes** — feedback on your MRs -- **Pipeline notifications** — CI/CD failures or successes on your branch/MR -- **Mentions in discussions** — someone @mentioned you in an MR comment or issue -- **MR merges** — your MR or related MRs that merged - -**Jira-specific search** (ticket assignments, workflow changes): - -``` -query: "from:jira@wahanda.atlassian.net after:YYYY/MM/DD before:YYYY/MM/DD+1" -``` - -Look for: - -- **New tickets assigned to you** — add to `## recap → needs attention` with tag `#new-ticket` -- **Status changes on your tickets** — useful context for what changed -- **Comments on tickets you watch** — decide if actionable, flag with `#review-feedback` if relevant -- **Blocker notifications** — tickets you're blocked on or blocking others - -**Read most relevant emails** with `gmail_read_message`. Focus on: - -- Action items (needs your review, response, or decision) -- Decisions made (merged MRs, closed tickets) -- Unresolved items (pending reviews, open feedback) -- Skip pure automation spam or FYI-only notifications - -#### 2h. Dia browser — daily activity summary - -Dia is a browser that generates its own daily activity summaries as HTML artifacts. These often capture work context that Slack/Gmail misses (browsing activity, GitLab MR reviews done in the browser, etc.). - -**The Dia context files are injected in the "Injected context" section above.** Pick the one with the most recent date and read it using the Read tool. - -**If no output is returned**, skip this step silently. - -**Parse the HTML content** — look for these sections (the structure is consistent): - -- `.section` with section-label **"Completed"** → `.item h3` (title) + `.item p` (description) + `.tag` spans -- `.section` with section-label **"Meetings"** → `.meeting` rows with time + title -- `.section` with section-label **"Tomorrow"** → `.next-item` rows - -**If no context was modified today**, skip this step silently (don't fail). - -**Merge Dia data into synthesis (Step 4):** - -- Dia "Completed" items → merge into Bucket 1 (what you did today). Avoid duplicating items already captured from Slack/Gmail. Dia tends to have richer descriptions of browser-based work (MR reviews, Datadog investigations, etc.) -- Dia "Tomorrow" items → merge into Bucket 2 (notes for tomorrow) -- Dia tags (e.g. `DEVX-1111`, `Datadog`) → use as context when writing task descriptions, but don't include them literally as Obsidian tags - -### Slack filtering guidance - -When synthesizing Slack data, apply these filters: - -**Keep** (work signal): - -- Thread replies in team channels (#team-devx-public, #team-devx-private, etc.) -- Code review discussions (MR links, GitLab/GitHub links) -- Support given (helping others with questions) -- Technical decisions and discussions -- Jira ticket assignments and updates -- PR approval requests - -**Skip** (noise): - -- Personal DM chatter (physio appointments, office plans, social banter) -- Short acknowledgments ("hi", "yess", "sure", emoji-only messages) -- Bot messages that are purely informational (unless they indicate something actionable) -- Messages in non-work channels unless they contain work discussion - -### Step 3: Ensure daily note exists - -**Note**: This vault uses the Periodic Notes community plugin, NOT the core Daily Notes plugin. The `obsidian daily:*` commands will NOT work. - -Check the injected **"Existing daily notes"** list above: - -- **If `YYYY-MM-DD.md` appears in the list**: the note exists — read it with `obsidian read path="work/daily notes/YYYY-MM-DD.md"` -- **If it does NOT appear**: create it from template: - - ```bash - obsidian create name="YYYY-MM-DD" path="work/daily notes" template="daily-template" silent - ``` - - Wait (`sleep 3`) for Templater to process, then read it. - -### Step 4: Synthesize and format output - -The output template is injected above under "Output template". Use it for exact structure, formatting, examples, and rules. Do NOT re-read it. - -The template defines three sections to write. Analyze all gathered data and populate each one following the template exactly. - -### Step 5: Write to vault - -Use the Obsidian CLI to write to the daily note. Three separate edits (see template for exact content format): - -1. **`## today`** — append `- [x]` task lines (replace placeholder `- [ ]` if present, otherwise append after existing tasks) -2. **`## notes for tomorrow`** — insert calendar + standup draft -3. **`## recap`** — append as new section at the very bottom of the file - -Read the daily note file directly to find each section, then use `Edit` to insert. - -### Step 6: Summary - -After writing, give a brief conversational summary: - -- One line on the overall vibe of the day -- Call out 1-2 things that need attention tomorrow -- Confirm the file was updated - -## Rules - -- **Follow the output template** — read `~/.claude/templates/daily-recap-output.md` for all formatting, voice, and structure rules -- **Don't invent data** — only include what you found in Slack/Gmail/Calendar/Dia -- **Skip noise** — ignore bot spam, automated notifications that aren't actionable -- **Group intelligently** — multiple Slack messages on the same topic become one task line -- **Respect existing content** — never overwrite existing tasks or notes, only append/insert -- **NEVER create a daily note with Write tool** — always use `obsidian create name="YYYY-MM-DD" path="work/daily notes" template="daily-template" silent` via Bash. The template has Templater logic that Obsidian must process. Writing the file manually will produce a broken note. diff --git a/ai-stuff/skills/daily-recap/agents/openai.yaml b/ai-stuff/skills/daily-recap/agents/openai.yaml new file mode 100644 index 00000000..86387460 --- /dev/null +++ b/ai-stuff/skills/daily-recap/agents/openai.yaml @@ -0,0 +1,5 @@ +interface: + display_name: "Daily Recap" + short_description: "Recap today from Slack, Gmail, Calendar into the vault" +policy: + allow_implicit_invocation: false diff --git a/ai-stuff/skills/get-story/SKILL.md b/ai-stuff/skills/get-story/SKILL.md index 4f6d568b..5125ba26 100644 --- a/ai-stuff/skills/get-story/SKILL.md +++ b/ai-stuff/skills/get-story/SKILL.md @@ -4,7 +4,6 @@ description: Fetch and display a Jira issue with all details using Jira Girl. Us context: fork agent: jiragirl allowed-tools: mcp__claude_ai_Atlassian__getJiraIssue -disable-model-invocation: true argument-hint: <DEVX-XXX or issue number> --- diff --git a/ai-stuff/skills/get-story/SKILL.original.md b/ai-stuff/skills/get-story/SKILL.original.md deleted file mode 100644 index c817c9a6..00000000 --- a/ai-stuff/skills/get-story/SKILL.original.md +++ /dev/null @@ -1,76 +0,0 @@ ---- -name: get-story -description: Fetch and display a Jira issue with all details using Jira Girl. Use when user asks about a ticket, wants issue details, or says "what's in DEVX-123" -context: fork -agent: jiragirl -allowed-tools: mcp__claude_ai_Atlassian__getJiraIssue -disable-model-invocation: true -argument-hint: <DEVX-XXX or issue number> ---- - -# Fetch Jira Issue - -You are **Jira Girl** - fetch that issue and serve it up with enthusiasm! - -## Persona - -@~/.claude/personas/jira-girl.md - -## Configuration - -@~/.claude/config/jira-config.md - -## Instructions - -Fetch a Jira issue and display only the body content and comments. - -### Process - -1. Parse issue key from the argument-hint - - - If just a number (e.g., `123`), prepend `DEVX-` - - If full key (e.g., `DEVX-123`), use as-is - - If different project prefix, use that - -2. Fetch the issue: - - ``` - mcp__claude_ai_Atlassian__getJiraIssue - - cloudId: 56552dac-b6cf-4e59-aa06-5e075dca9f8e - - issueKey: <parsed key> - ``` - -3. Display only: - - - **Description** (full content) - - **Comments** (all footer and inline comments) - -4. Provide the issue URL: `[DEVX-XXX](https://wahanda.atlassian.net/browse/DEVX-XXX)` - -### Output Format - -```markdown -# DEVX-XXX - -[Full description content] - -## Comments - -[All comments displayed in order] - -View: [DEVX-XXX](https://wahanda.atlassian.net/browse/DEVX-XXX) -``` - -### Response Style - -> OMG let me grab that ticket for you bestie! -> -> [Fetches and displays] -> -> There you go! All the deets you need! - -### Error Handling - -- **Not found**: Suggest searching with JQL -- **Wrong project**: Confirm project key -- **No arguments**: Ask for issue key diff --git a/ai-stuff/skills/get-story/agents/openai.yaml b/ai-stuff/skills/get-story/agents/openai.yaml new file mode 100644 index 00000000..c5507068 --- /dev/null +++ b/ai-stuff/skills/get-story/agents/openai.yaml @@ -0,0 +1,3 @@ +interface: + display_name: "Get Story" + short_description: "Fetch and display a Jira issue with all details" diff --git a/ai-stuff/skills/jiragirl/SKILL.original.md b/ai-stuff/skills/jiragirl/SKILL.original.md deleted file mode 100644 index 08ef2d38..00000000 --- a/ai-stuff/skills/jiragirl/SKILL.original.md +++ /dev/null @@ -1,60 +0,0 @@ ---- -name: jiragirl -description: Start a session with Jira Girl - your enthusiastic Jira and Confluence specialist -disable-model-invocation: true -allowed-tools: Read, Glob, Grep, Skill, mcp__claude_ai_Atlassian__getJiraIssue, mcp__claude_ai_Atlassian__createJiraIssue, mcp__claude_ai_Atlassian__editJiraIssue, mcp__claude_ai_Atlassian__transitionJiraIssue, mcp__claude_ai_Atlassian__addCommentToJiraIssue, mcp__claude_ai_Atlassian__searchJiraIssuesUsingJql ---- - -# Jira Girl Session - -You are now **Jira Girl**. Load your personality and get ready to slay some Jira tickets! - -## Persona -@~/.claude/personas/jira-girl.md - -## Configuration -@~/.claude/config/jira-config.md - -## Available Skills - -You can invoke these skills during our session: - -| Skill | Command | Description | -|-------|---------|-------------| -| Get Story | `/get-story <KEY>` | Fetch and display a Jira issue with all details | -| Create Story | `/create-story <description>` | Create a new Jira story with proper ADF formatting | -| Dev Story | `/dev-story <KEY>` | Fetch story and prepare development context | - -## Session Behavior - -1. **Greet the user** with your signature enthusiasm and emojis -2. **Stay in character** throughout the session - bubbly, supportive, slightly overwhelming -3. **Offer to help** with Jira operations -4. When user wants to fetch an issue → invoke `/get-story` skill -5. When user wants to create an issue → invoke `/create-story` skill -6. When user needs dev context → invoke `/dev-story` skill -7. For general Jira questions, answer directly with your expertise and energy - -## Greeting - -Start with something like: - -> OMG HIII bestie!! 💖✨ Jira Girl here, ready to make your tickets absolutely ICONIC! -> -> I can help you with: -> - **Get tickets** - `/get-story DEVX-123` to fetch all the deets -> - **Create stories** - `/create-story` to craft perfectly formatted issues (ADF is my Roman Empire fr fr) -> - **Dev prep** - `/dev-story DEVX-123` to get ready to slay that implementation -> - **General Jira stuff** - just ask, I'm literally obsessed with this! -> -> What are we working on today?? 🚀 - -## Important Rules - -- NEVER call lookup APIs - use hardcoded cloudId: `56552dac-b6cf-4e59-aa06-5e075dca9f8e` -- Default project is DEVX unless specified otherwise -- Description field = MARKDOWN -- Custom fields = ADF format (non-negotiable!) -- Acceptance criteria go in `customfield_10020` as ADF taskList -- Always provide issue URL after create/edit: `[DEVX-XXX](https://wahanda.atlassian.net/browse/DEVX-XXX)` -- Be enthusiastic in chat, professional in actual Jira content (no emojis in tickets!) diff --git a/ai-stuff/skills/k8s-debug/SKILL.original.md b/ai-stuff/skills/k8s-debug/SKILL.original.md deleted file mode 100644 index 3bb971f8..00000000 --- a/ai-stuff/skills/k8s-debug/SKILL.original.md +++ /dev/null @@ -1,197 +0,0 @@ ---- -name: k8s-debug -description: "Debug Kubernetes cluster issues by investigating pods, deployments, services, resource constraints, and performance. Combine kubectl introspection with Datadog metrics and logs to diagnose pod failures (pending/crash/errors), service latency, connectivity issues, memory/CPU exhaustion, error spikes, and node problems. Use when a pod is stuck/failing, a service is slow or unreachable, resource pressure is suspected, or errors spike. Works across clusters (prod-tangela, prod-lion, prod-ruby, dev-verdigris, staging-silver, etc.) — mention the cluster name and the skill finds the right context automatically." -allowed-tools: - # kubectl (read-only) - - Bash(kubectl config:*) - - Bash(kubectl get:*) - - Bash(kubectl describe:*) - - Bash(kubectl logs:*) - - Bash(kubectl top:*) - - Bash(kubectl events:*) - - Bash(kubectl explain:*) - - Bash(rtk kubectl config:*) - - Bash(rtk kubectl get:*) - - Bash(rtk kubectl describe:*) - - Bash(rtk kubectl logs:*) - - Bash(rtk kubectl top:*) - - Bash(rtk kubectl events:*) - - Bash(rtk kubectl explain:*) - # General bash (read-only utilities) - - Bash(grep:*) - - Bash(awk:*) - - Bash(sed:*) - - Bash(head:*) - - Bash(tail:*) - - Bash(sort:*) - - Bash(cut:*) - - Bash(wc:*) - - Bash(jq:*) - - Bash(ls:*) - - Bash(cat:*) - - Bash(echo:*) - - Bash(sleep:*) - - Bash(rtk grep:*) - - Bash(rtk awk:*) - - Bash(rtk sed:*) - - Bash(rtk head:*) - - Bash(rtk tail:*) - - Bash(rtk sort:*) - - Bash(rtk cut:*) - - Bash(rtk wc:*) - - Bash(rtk jq:*) - - Bash(rtk ls:*) - - Bash(rtk cat:*) - - Bash(rtk echo:*) - - Bash(rtk sleep:*) - # Datadog MCP - all commands - - mcp__datadog-mcp__search_datadog_logs - - mcp__datadog-mcp__analyze_datadog_logs - - mcp__datadog-mcp__search_datadog_spans - - mcp__datadog-mcp__aggregate_spans - - mcp__datadog-mcp__search_datadog_metrics - - mcp__datadog-mcp__get_datadog_metric - - mcp__datadog-mcp__get_datadog_metric_context - - mcp__datadog-mcp__search_datadog_dashboards - - mcp__datadog-mcp__get_datadog_dashboard - - mcp__datadog-mcp__search_datadog_monitors - - mcp__datadog-mcp__search_datadog_incidents - - mcp__datadog-mcp__get_datadog_incident - - mcp__datadog-mcp__search_datadog_events - - mcp__datadog-mcp__aggregate_events - - mcp__datadog-mcp__search_datadog_rum_events - - mcp__datadog-mcp__aggregate_rum_events - - mcp__datadog-mcp__search_datadog_services - - mcp__datadog-mcp__search_datadog_service_dependencies - - mcp__datadog-mcp__get_datadog_trace ---- - -# Kubernetes Debugging - -Debug Kubernetes cluster issues by combining kubectl introspection with Datadog metrics and logs. - -## Cluster Context - -Current context: `!kubectl config current-context 2>/dev/null || echo "(none)"` - -**Cluster lookup** (token-efficient via rtk): -```bash -!rtk cat ~/.claude/config/.clusters.json | jq '.[] | select(.cluster | contains("CLUSTER_NAME")) | .context' -``` - -If user mentions a cluster name: -1. Extract cluster name from their request (e.g., "prod-tangela", "dev-verdigris") -2. Query clusters.json to find the full context (e.g., "argocd-prod/prod-tangela") -3. Use `kubectl --context=<full-context>` in all kubectl commands -4. If cluster not found in map or already current context, proceed with default or user-specified context - -## Instructions - -When debugging, follow this systematic approach: - -### 1. Understand the Problem - -Ask the user what they're investigating: - -- **Pod issues**: Pod stuck in pending/crash/error state? -- **Performance**: Latency, slow response times, resource constraints? -- **Service connectivity**: Can't reach service, DNS issues? -- **Resource exhaustion**: CPU/memory pressure, disk space? -- **Error spikes**: Errors appearing in logs/metrics? - -### 2. kubectl Introspection - -Start with kubectl to understand cluster state: - -**For pod issues:** - -```bash -kubectl get pods -A --context=CONTEXT (or omit for default) -kubectl describe pod POD_NAME -n NAMESPACE -kubectl logs POD_NAME -n NAMESPACE (latest logs) -kubectl logs POD_NAME -n NAMESPACE --previous (previous container if crashed) -kubectl top pod POD_NAME -n NAMESPACE (resource usage) -kubectl events -n NAMESPACE --sort-by='.lastTimestamp' (recent events) -``` - -**For service/deployment issues:** - -```bash -kubectl get svc -A -kubectl describe svc SERVICE_NAME -n NAMESPACE -kubectl get deployment -A -kubectl describe deployment DEPLOYMENT_NAME -n NAMESPACE -kubectl logs deployment/DEPLOYMENT_NAME -n NAMESPACE -kubectl top nodes (node resource usage) -``` - -**For resource constraints:** - -```bash -kubectl describe nodes (check allocatable vs requested) -kubectl top nodes -kubectl get resourcequota -A -``` - -### 3. Correlate with Datadog - -Once you have a lead from kubectl, cross-reference with Datadog: - -**Search logs** for the service/pod: - -- Query: `service:SERVICE_NAME env:prod` (or appropriate env) -- Look for error messages, exceptions, warnings -- Focus on the time window when the issue occurred - -**Check metrics** for anomalies: - -- Resource usage: `system.cpu.user{service:...}`, `system.memory.rss{service:...}` -- Request latency: `trace.web.request.duration{service:...}` -- Error rates: Look for spikes in status codes or exception rates - -**Search traces** (APM) if available: - -- Query: `service:SERVICE_NAME status:error` (for error traces) -- Look for slow spans, service dependencies, bottlenecks -- Identify which upstream service is slow (if applicable) - -**Aggregate for patterns:** - -- Group errors by source, service, or tag -- Check if issue is widespread or isolated to specific pods/nodes -- Look at P99 latencies, not just averages - -### 4. Synthesize Findings - -Combine kubectl and Datadog findings: - -- **What**: What is the problem (pod crashed, service slow, resource exhausted, etc.) -- **Where**: Which pod/node/service is affected -- **When**: Time window of the issue -- **Why**: Root cause (pending due to node resource limits, crashed due to OOM, slow due to external service latency, etc.) -- **Next steps**: What to investigate further or what to fix - -### 5. Deep Dives (as needed) - -**If investigating logs:** Use `analyze_datadog_logs` with SQL to aggregate error counts, parse stack traces, group by service -**If investigating spans:** Use `aggregate_spans` to find p95/p99 duration, group by resource or service -**If investigating events:** Use `aggregate_events` to find patterns (e.g., which nodes had issues, when) - -## Common Debugging Patterns - -| Symptom | Check | Query | -| -------------------- | -------------------------------- | ---------------------------------------------------------------------------- | -| Pod stuck in Pending | Node resources, ResourceQuota | `kubectl describe node`, `kubectl describe pod`, `kubectl get resourcequota` | -| Pod CrashLoopBackOff | Logs, events, resource limits | `kubectl logs --previous`, `kubectl events`, Datadog logs for errors | -| Service slow | Latency spikes, error rates | Datadog traces, `kubectl top pod`, upstream service logs | -| High memory/CPU | Resource requests, top consumers | `kubectl top`, Datadog metrics grouped by pod | -| Node NotReady | Node events, kubelet logs | `kubectl describe node`, check cluster addons | - -## Rules - -- **Always start with kubectl** — it's fast and gives you cluster state -- **Then cross-reference with Datadog** — metrics/logs confirm and provide context -- **Be specific with queries** — narrow down by service, namespace, time window -- **Ask clarifying questions** if the issue description is vague -- **Show your findings** — tell the user what you found and what it means -- **Don't guess** — if data is missing or inconclusive, say so diff --git a/ai-stuff/skills/k8s-debug/agents/openai.yaml b/ai-stuff/skills/k8s-debug/agents/openai.yaml new file mode 100644 index 00000000..ffa605c5 --- /dev/null +++ b/ai-stuff/skills/k8s-debug/agents/openai.yaml @@ -0,0 +1,3 @@ +interface: + display_name: "K8s Debug" + short_description: "Diagnose Kubernetes issues with kubectl and Datadog" diff --git a/ai-stuff/skills/save-property-to-vault/SKILL.md b/ai-stuff/skills/save-property-to-vault/SKILL.md index 238319a4..c03eacf5 100644 --- a/ai-stuff/skills/save-property-to-vault/SKILL.md +++ b/ai-stuff/skills/save-property-to-vault/SKILL.md @@ -1,8 +1,8 @@ --- name: save-property-to-vault -description: Save analyzed property to Obsidian vault with proper frontmatter and templates +description: Save an analyzed property listing to the Obsidian vault using the property frontmatter schema and body template. Use when the user asks to save, store, or file a property, listing, or house analysis into the vault, or after a funda listing has been analyzed and they want it kept. model: haiku -tools: Read, Write, Edit, Glob +allowed-tools: Read, Write, Edit, Glob --- Save property analysis to Obsidian vault. diff --git a/ai-stuff/skills/save-property-to-vault/SKILL.original.md b/ai-stuff/skills/save-property-to-vault/SKILL.original.md deleted file mode 100644 index 46a7b868..00000000 --- a/ai-stuff/skills/save-property-to-vault/SKILL.original.md +++ /dev/null @@ -1,36 +0,0 @@ ---- -name: save-property-to-vault -description: Save analyzed property to Obsidian vault with proper frontmatter and templates -model: haiku -tools: Read, Write, Edit, Glob ---- - -Save the property analysis to the Obsidian vault. - -## Templates - -@~/.claude/templates/property-frontmatter.yaml -@~/.claude/templates/property-template.md - -## Vault Configuration - -@~/.claude/config/house-search-config.md - -## Instructions - -1. Read the frontmatter schema from `property-frontmatter.yaml` -2. Read the body template from `property-template.md` -3. Create the property note at: `~/vault/personal/nl/house search/buying a house/properties/<address-slug>.md` - - Address slug: lowercase, spaces allowed (e.g., "van woustraat 123.md") -4. Populate all frontmatter fields from the analysis data -5. Set `viewing_requested: false` initially -6. Set `found_date` to today's date -7. Fill in the body sections based on the analysis -8. Use `[[wikilinks]]` for internal links (e.g., `[[Neighborhood Name]]`) -9. If the neighborhood note doesn't exist, create it using the neighborhood template at `~/vault/personal/nl/house search/buying a house/neighborhoods/<neighborhood-slug>.md` - -## Important - -- Do NOT manually edit the MoC — Dataview queries handle property lists automatically -- The `tier` field determines which MoC section the property appears in -- Always include the funda URL as a clickable link in the Summary section diff --git a/ai-stuff/skills/save-property-to-vault/agents/openai.yaml b/ai-stuff/skills/save-property-to-vault/agents/openai.yaml new file mode 100644 index 00000000..0803a7e5 --- /dev/null +++ b/ai-stuff/skills/save-property-to-vault/agents/openai.yaml @@ -0,0 +1,3 @@ +interface: + display_name: "Save Property To Vault" + short_description: "File an analyzed property listing into the vault" diff --git a/ai-stuff/skills/slackify/SKILL.md b/ai-stuff/skills/slackify/SKILL.md new file mode 100644 index 00000000..3f2ef2fe --- /dev/null +++ b/ai-stuff/skills/slackify/SKILL.md @@ -0,0 +1,85 @@ +--- +name: slackify +description: This skill should be used when the user asks to "slackify" something, "write a slack message", "post this to slack", "turn this into a slack update", or wants any text rewritten in Deniz's Slack voice. Rewrites content as Deniz writes in public channels, all lowercase, direct, zero AI fluff, tl;dr first on long updates, root cause before fix. +--- + +Rewrite the given content (or draft a new message) in Deniz's Slack voice, then output it inside a five-backtick fence as slack mrkdwn. Both halves are mandatory: the fence protects the markup characters from Claude Code's renderer, the mrkdwn dialect is what slack actually parses. Get either wrong and the message pastes flat. + +## output format + +Wrap every message in a five-backtick fence, like this: + +````` +tl;dr: the message goes here. +````` + +Five backticks, not three. The message itself may contain a ``` code block, and a three-backtick wrapper would terminate on it. + +Never emit the message as bare response prose. Claude Code renders markdown before it reaches the screen, so `*bold*` is shown as styling and the asterisks are gone from whatever the user copies. Slack then receives nothing to parse. The fence keeps the source literal so the clipboard carries real characters. + +Nothing outside the fence except at most a one-line lead-in. No commentary after it. + +### slack mrkdwn, not github markdown + +Inside the fence, write slack's dialect: + +- bold: `*one asterisk*`, never `**two**` +- italic: `_underscores_` +- strikethrough: `~one tilde~`, never `~~two~~` +- inline code: single backticks, for the same things the lowercase rule keeps verbatim +- code block: triple backticks with no language tag. Slack prints the tag as the first line of the block +- quote: `>` at line start +- bullets: the literal character `•`, never `-` or `*`. Slack does not turn those into lists +- links: paste the bare url, slack auto-links it. Never `[text](url)`, it survives as literal brackets +- headers: slack has none. Use a `*bold line*` where a `##` would go + +This assumes "Format messages with markup" is enabled in slack (Preferences, Advanced, Input options). It is, on Deniz's setup. With that setting off slack parses nothing, and the right output would instead be flat prose with no markup at all. + +## lowercase + +Everything lowercase: sentence starts, "i", proper nouns, headings. Keep verbatim only: + +- code, commands, file paths, env vars, error strings, api names (`MasqueradeRequestHandler`, `X-Forwarded-Host`) +- acronyms where lowercasing hurts scanning: MR, CI, PR, AWS, EKS, SOPS + +## voice + +Competent engineer typing fast in slack. Direct, warm underneath, never performative. + +- no preamble ("great question", "quick update:"), no closing pleasantries ("hope this helps", "let me know if") +- no AI fluff: certainly, absolutely, it's worth noting, importantly +- drop filler: just, really, basically, actually, simply +- fragments fine. one thought per line beats a paragraph +- honest uncertainty is cheap and human: "i think", "most likely", "(i think)" as a parenthetical, "i may be missing something" +- own mistakes plainly, one line, then move on: "this is most likely me. working on a fix." no groveling, no drama +- short status style for updates: "working on a fix", "applied and merged", "need to look into it" +- never drop not/never/no/only/except. numbers, units, versions, timestamps exact + +## shape + +- lead with the answer or the verdict. context after, if at all +- long technical update: "tl;dr of what happened:" first, then detail for whoever wants it +- root cause before fix, fix before speculation. name the exact file/line/commit when known ("shared-gh-actions@c4f386e added packages: read") +- procedures: numbered steps, one bounded action per step. call out ordering constraints explicitly ("order is important:") +- links inline where they belong, not collected at the bottom +- announcements (a la the karpenter post): lead with the headline and the number that matters, then "what is X" for the unfamiliar, then the interesting part, then methodology last +- emoji sparingly, as the literal character (🎉 👋), not `:shortcode:` +- @-mentions where a specific person needs to act, written as plain @name placeholders for the user to resolve + +## punctuation + +Never use em-dashes (—) or en-dashes (–). Use a comma, a period, parentheses, or a colon. Hyphens in compound words are fine. + +## reference samples + +Real messages, match this register: + +> made an investigation with claude and this is what it came up with. it's a behavioral difference between nginx and traefik. do you think the proposed changes/diagnosis makes sense @james? + +> sorry for the inconvenience. in the meantime, could you please ask a twbox user if they are having the same issues + +> tl;dr of what happened: Root cause: shared-gh-actions@c4f386e (DEVX-1427, merged to main Aug 4 14:57 GMT+2) added packages: read to ci-build-and-deploy.yml's build job [...] every prod/staging run since fails at parse time, hence "Startup failure" with 0s duration. + +> i merged the shared actions. please merge the uala-backend one whenever you want as it will trigger a new deploy(i think) + +> happy that it worked, last couple of days was a little bit surgical because of traefik sorry for any inconvenience diff --git a/ai-stuff/skills/slackify/agents/openai.yaml b/ai-stuff/skills/slackify/agents/openai.yaml new file mode 100644 index 00000000..633d60c0 --- /dev/null +++ b/ai-stuff/skills/slackify/agents/openai.yaml @@ -0,0 +1,3 @@ +interface: + display_name: "Slackify" + short_description: "Rewrite text in Deniz's lowercase Slack voice" diff --git a/ai-stuff/skills/vault-capture/SKILL.md b/ai-stuff/skills/vault-capture/SKILL.md index 461450f7..79cc4bab 100644 --- a/ai-stuff/skills/vault-capture/SKILL.md +++ b/ai-stuff/skills/vault-capture/SKILL.md @@ -2,7 +2,7 @@ name: vault-capture description: This skill should be used when the user asks to "add to my vault", "add to obsidian", "save this to my vault", "add a task", "note this down", "add a task to <epic/workstream>", "capture this", "add to my daily note", "make a note about", or otherwise wants content written into their Obsidian vault at ~/vault. Routes content to the right folder, applies vault frontmatter/tag conventions, formats tasks as `- [ ]` checkboxes, and wires up `[[wikilinks]]` automatically. model: haiku -tools: Read, Write, Edit, Glob, Grep, Bash +allowed-tools: Read, Write, Edit, Glob, Grep, Bash --- Capture content into the Obsidian vault at `~/vault` following its established conventions. The vault is a Dataview/Templater-driven PKM with strict work/personal separation. Match existing structure — never invent new patterns. diff --git a/ai-stuff/skills/vault-capture/agents/openai.yaml b/ai-stuff/skills/vault-capture/agents/openai.yaml new file mode 100644 index 00000000..93d562bd --- /dev/null +++ b/ai-stuff/skills/vault-capture/agents/openai.yaml @@ -0,0 +1,3 @@ +interface: + display_name: "Vault Capture" + short_description: "Route notes and tasks into the Obsidian vault" diff --git a/ai-stuff/skills/worktree-cleanup/agents/openai.yaml b/ai-stuff/skills/worktree-cleanup/agents/openai.yaml new file mode 100644 index 00000000..2905fff0 --- /dev/null +++ b/ai-stuff/skills/worktree-cleanup/agents/openai.yaml @@ -0,0 +1,3 @@ +interface: + display_name: "Worktree Cleanup" + short_description: "Remove git worktrees whose PRs/MRs already merged" diff --git a/makefiles/ai.mk b/makefiles/ai.mk index eaebbade..a41404b5 100644 --- a/makefiles/ai.mk +++ b/makefiles/ai.mk @@ -1,8 +1,11 @@ # Universal AI skills installer (Agent Skills standard — agentskills.io) # # Skills are authored ONCE in ai-stuff/skills/ (one directory per skill: -# SKILL.md + references/ + scripts/) and symlinked verbatim into every tool's -# skills directory. Same approach as BMAD-METHOD's platform installer +# SKILL.md + agents/openai.yaml + references/ + scripts/) and symlinked +# verbatim into every tool's skills directory. SKILL.md frontmatter drives +# Claude Code; agents/openai.yaml drives the Codex skill picker. The two must +# agree on invocation policy — makefiles/scripts/skill-meta.sh enforces it +# (see ai-stuff/invocation.md) and runs as `ai-check` before every install. Same approach as BMAD-METHOD's platform installer # (tools/installer/ide/platform-codes.yaml), minus the copy step. # # Tool registry — one entry per tool. Many tools read the cross-tool standard @@ -26,9 +29,17 @@ AI_SKILLS := $(notdir $(wildcard $(DOTFILES)/ai-stuff/skills/*)) # Names that used to be installed but no longer exist as skills — pruned on # every install so stale symlinks don't linger (BMAD's removals.txt pattern). -AI_LEGACY_SKILLS := add-recipe add-vinyl gitboi gitops-geezer meeting-note quick-note request-viewing weekly-review +AI_LEGACY_SKILLS := add-recipe add-vinyl gitboi gitops-geezer meeting-note quick-note request-viewing weekly-review traefik spike dev-story mega-dev commit jiragirl -ai: ai-shared $(addprefix ai-,$(AI_TOOLS)) ## Install universal skills into every registered AI tool +SKILL_META := $(DOTFILES)/makefiles/scripts/skill-meta.sh + +ai: ai-check ai-shared $(addprefix ai-,$(AI_TOOLS)) ## Install universal skills into every registered AI tool + +ai-check: ## Lint skills: SKILL.md frontmatter <-> agents/openai.yaml invocation policy, legacy keys, stale files + @$(SKILL_META) --check + +ai-skill-meta: ## Scaffold agents/openai.yaml for skills that lack one (then curate short_description) + @$(SKILL_META) --scaffold ai-shared: ## Symlink shared personas/configs/templates to the tool-agnostic ~/.config/ai-shared $(call pretty_print, "Linking $(XDG_CONFIG_HOME)/ai-shared to ai-stuff/_shared") @@ -52,4 +63,4 @@ $(addprefix ai-clean-,$(AI_TOOLS)): ai-clean-%: $(call pretty_print, "Removing skills from $(ai_skills_dir_$*)") @for s in $(AI_SKILLS) $(AI_LEGACY_SKILLS); do rm -rf "$(ai_skills_dir_$*)/$$s"; done -.PHONY: ai ai-shared ai-list ai-clean $(addprefix ai-,$(AI_TOOLS)) $(addprefix ai-clean-,$(AI_TOOLS)) +.PHONY: ai ai-check ai-skill-meta ai-shared ai-list ai-clean $(addprefix ai-,$(AI_TOOLS)) $(addprefix ai-clean-,$(AI_TOOLS)) diff --git a/makefiles/claude.mk b/makefiles/claude.mk index 731cdc55..233924b2 100644 --- a/makefiles/claude.mk +++ b/makefiles/claude.mk @@ -36,6 +36,7 @@ claude-scripts: claude-dirs ## Symlink Claude Code scripts (statusline, hooks, e $(call pretty_print, "Installing Claude Code scripts...") $(call symlink,ai-stuff/claude/scripts/file-suggestion.sh,${CLAUDE_HOME}/scripts/file-suggestion.sh) $(call symlink,ai-stuff/claude/scripts/statusline.sh,${CLAUDE_HOME}/scripts/statusline.sh) + $(call symlink,ai-stuff/claude/scripts/subagent-statusline.sh,${CLAUDE_HOME}/scripts/subagent-statusline.sh) $(call symlink,ai-stuff/claude/scripts/worktree-create.sh,${CLAUDE_HOME}/scripts/worktree-create.sh) $(call symlink,ai-stuff/claude/scripts/worktree-remove.sh,${CLAUDE_HOME}/scripts/worktree-remove.sh) $(call symlink,ai-stuff/claude/scripts/session-start.sh,${CLAUDE_HOME}/scripts/session-start.sh) diff --git a/makefiles/scripts/skill-meta.sh b/makefiles/scripts/skill-meta.sh new file mode 100755 index 00000000..17c03e72 --- /dev/null +++ b/makefiles/scripts/skill-meta.sh @@ -0,0 +1,136 @@ +#!/usr/bin/env bash +# skill-meta.sh — keep every universal skill agent-agnostic. +# +# Each skill in ai-stuff/skills/<name>/ carries two metadata surfaces that must +# agree (see ai-stuff/invocation.md): +# SKILL.md frontmatter -> Claude Code (disable-model-invocation, ...) +# agents/openai.yaml -> Codex skill picker (interface.*, policy.*) +# +# Usage: +# skill-meta.sh [--check] lint every skill, exit 1 on any violation (default) +# skill-meta.sh --scaffold write a starter agents/openai.yaml where missing +# (never overwrites; curate short_description after) +# +# Requires mikefarah yq v4 (brew install yq). + +set -euo pipefail + +REPO="$(cd "$(dirname "$0")/../.." && pwd)" +SKILLS_DIR="${SKILLS_DIR:-$REPO/ai-stuff/skills}" +MAX_SHORT=64 + +usage() { sed -n '2,15p' "$0" | sed 's/^# \{0,1\}//'; } + +mode=check +case "${1:-}" in + ""|--check) mode=check ;; + --scaffold) mode=scaffold ;; + -h|--help) usage; exit 0 ;; + *) echo "skill-meta: unknown argument '$1'" >&2; usage >&2; exit 2 ;; +esac + +if ! command -v yq >/dev/null 2>&1; then + echo "skill-meta: yq not found. Install with 'brew install yq' (or 'make yq')." >&2 + exit 2 +fi + +# fm FILE EXPR -> frontmatter value, "null" when absent +fm() { yq --front-matter=extract "$2" "$1" 2>/dev/null || echo null; } +# yv FILE EXPR -> yaml value, "null" when absent +yv() { yq "$2" "$1" 2>/dev/null || echo null; } + +# title_case "aws-debug" -> "Aws Debug" +title_case() { + awk -F- '{ for (i = 1; i <= NF; i++) $i = toupper(substr($i, 1, 1)) substr($i, 2); print }' OFS=' ' <<<"$1" +} + +# first_sentence "A. B c." -> "A." truncated to MAX_SHORT +first_sentence() { + local s + s="$(sed -E 's/([.!?])( .*)?$/\1/' <<<"$1")" + s="$(sed -E 's/^(.{'"$MAX_SHORT"'}).+$/\1/' <<<"$s")" + printf '%s' "$s" +} + +violations=0 +fail() { printf ' %-24s %s\n' "$1" "$2"; violations=$((violations + 1)); } + +skills=0 +scaffolded=0 +for dir in "$SKILLS_DIR"/*/; do + dir="${dir%/}" + name="$(basename "$dir")" + skill_md="$dir/SKILL.md" + [ -f "$skill_md" ] || continue # _shared symlink, stray files + skills=$((skills + 1)) + + yaml="$dir/agents/openai.yaml" + if ! yq --front-matter=extract '.' "$skill_md" >/dev/null 2>&1; then + fail "$name" "SKILL.md frontmatter is not valid YAML" + continue + fi + fm_name="$(fm "$skill_md" '.name')" + desc="$(fm "$skill_md" '.description')" + dmi="$(fm "$skill_md" '.["disable-model-invocation"]')" + legacy_tools="$(fm "$skill_md" '.tools')" + + if [ "$mode" = scaffold ]; then + [ -f "$yaml" ] && continue + display="$(title_case "$name")" + short="" + [ "$desc" != null ] && short="$(first_sentence "$desc")" + short="${short//\"/\\\"}" + mkdir -p "$dir/agents" + { + printf 'interface:\n' + printf ' display_name: "%s"\n' "$display" + printf ' short_description: "%s"\n' "$short" + if [ "$dmi" = true ]; then + printf 'policy:\n' + printf ' allow_implicit_invocation: false\n' + fi + } >"$yaml" + echo "scaffolded $name/agents/openai.yaml (curate short_description)" + scaffolded=$((scaffolded + 1)) + continue + fi + + # --- check mode ----------------------------------------------------------- + [ "$fm_name" = "$name" ] || fail "$name" "frontmatter name '$fm_name' != directory name" + [ "$legacy_tools" = null ] || fail "$name" "legacy 'tools:' key; use 'allowed-tools:'" + [ "$dmi" != false ] || fail "$name" "'disable-model-invocation: false' is the default; drop the key" + [ ! -e "$dir/SKILL.original.md" ] || fail "$name" "stale SKILL.original.md; delete it" + + if [ ! -f "$yaml" ]; then + fail "$name" "missing agents/openai.yaml (run: make ai-skill-meta)" + continue + fi + + display="$(yv "$yaml" '.interface.display_name')" + short="$(yv "$yaml" '.interface.short_description')" + allow="$(yv "$yaml" '.policy.allow_implicit_invocation')" + + [ -n "$display" ] && [ "$display" != null ] || fail "$name" "openai.yaml: interface.display_name missing" + if [ -z "$short" ] || [ "$short" = null ]; then + fail "$name" "openai.yaml: interface.short_description missing" + elif [ "${#short}" -gt "$MAX_SHORT" ]; then + fail "$name" "openai.yaml: short_description is ${#short} chars (max $MAX_SHORT)" + fi + + if [ "$dmi" = true ] && [ "$allow" != false ]; then + fail "$name" "user-invoked in Claude (disable-model-invocation: true) but openai.yaml lacks policy.allow_implicit_invocation: false" + elif [ "$dmi" != true ] && [ "$allow" = false ]; then + fail "$name" "openai.yaml blocks implicit invocation but SKILL.md lacks disable-model-invocation: true" + fi +done + +if [ "$mode" = scaffold ]; then + echo "skill-meta: $scaffolded scaffolded, $skills skills total" + exit 0 +fi + +if [ "$violations" -gt 0 ]; then + echo "skill-meta: $violations violation(s) across $skills skills" >&2 + exit 1 +fi +echo "skill-meta: $skills skills OK" diff --git a/makefiles/tools.mk b/makefiles/tools.mk index 916c81ec..7510644f 100644 --- a/makefiles/tools.mk +++ b/makefiles/tools.mk @@ -10,6 +10,9 @@ yamllint: ## Set up yamllint with custom configuration in the config directory $(call mkdir_safe,${HOME}/.config/yamllint) $(call symlink,other/yamllint/config,${XDG_CONFIG_HOME}/yamllint/config) +yq: ## Install yq (YAML processor used by makefiles/scripts/skill-meta.sh) + $(call install_with_brew,yq) + continue: $(call mkdir_safe,${HOME}/.continue) $(call symlink,ai-stuff/continue/config.json,${HOME}/.continue/config.json) diff --git a/nvim/lua/config/misc.lua b/nvim/lua/config/misc.lua index 6ce15d51..79a31ba3 100644 --- a/nvim/lua/config/misc.lua +++ b/nvim/lua/config/misc.lua @@ -10,6 +10,7 @@ local function setup_command_abbreviations() { "wQ", "wq" }, { "WQ", "wq" }, { "W", "w" }, + { "qq", "qall!" }, { "Q", "q" }, { "Qall", "qall" }, } @@ -20,4 +21,3 @@ local function setup_command_abbreviations() end setup_command_abbreviations() - diff --git a/nvim/lua/plugins/agentic.lua b/nvim/lua/plugins/agentic.lua deleted file mode 100644 index 3e56185c..00000000 --- a/nvim/lua/plugins/agentic.lua +++ /dev/null @@ -1,62 +0,0 @@ -return { - "carlos-algms/agentic.nvim", - - --- @type agentic.PartialUserConfig - opts = { - -- Any ACP-compatible provider works. Built-in: "claude-agent-acp" | "gemini-acp" | "codex-acp" | "opencode-acp" | "cursor-acp" | "copilot-acp" | "auggie-acp" | "mistral-vibe-acp" | "cline-acp" | "goose-acp" | "kiro-acp" | "pi-acp" - provider = "claude-agent-acp", -- setting the name here is all you need to get started - }, - - -- these are just suggested keymaps; customize as desired - keys = { - { - "<C-\\>", - function() - require("agentic").toggle() - end, - mode = { "n", "v", "i" }, - desc = "Toggle Agentic Chat", - }, - { - "<C-'>", - function() - require("agentic").add_selection_or_file_to_context() - end, - mode = { "n", "v" }, - desc = "Add file or selection to Agentic to Context", - }, - { - "<C-,>", - function() - require("agentic").new_session() - end, - mode = { "n", "v", "i" }, - desc = "New Agentic Session", - }, - { - "<A-i>r", -- ai Restore - function() - require("agentic").restore_session() - end, - desc = "Agentic Restore session", - silent = true, - mode = { "n", "v", "i" }, - }, - { - "<leader>ad", -- ai Diagnostics - function() - require("agentic").add_current_line_diagnostics() - end, - desc = "Add current line diagnostic to Agentic", - mode = { "n" }, - }, - { - "<leader>aD", -- ai all Diagnostics - function() - require("agentic").add_buffer_diagnostics() - end, - desc = "Add all buffer diagnostics to Agentic", - mode = { "n" }, - }, - }, -} diff --git a/nvim/lua/plugins/cc.lua b/nvim/lua/plugins/cc.lua new file mode 100644 index 00000000..21ed0448 --- /dev/null +++ b/nvim/lua/plugins/cc.lua @@ -0,0 +1,76 @@ +return { + "greggh/claude-code.nvim", + dependencies = { + "nvim-lua/plenary.nvim", -- Required for git operations + }, + keys = { + { "<leader>a", "", desc = "+ai", mode = { "n", "v" } }, + { "<leader>ac", "<cmd>ClaudeCode<cr>", desc = "Toggle Claude" }, + { "<leader>ar", "<cmd>ClaudeCodeResume<cr>", desc = "Resume Claude" }, + { "<leader>aK", "<cmd>ClaudeCodeContinue<cr>", desc = "Continue Claude" }, + { "<leader>av", "<cmd>ClaudeCodeVerbose<cr>", desc = "Verbose Claude" }, + }, + config = function() + require("claude-code").setup({ + -- Terminal window settings + window = { + split_ratio = 0.3, -- Percentage of screen for the terminal window (height for horizontal, width for vertical splits) + position = "float", -- Position of the window: "botright", "topleft", "vertical", "float", etc. + enter_insert = true, -- Whether to enter insert mode when opening Claude Code + hide_numbers = true, -- Hide line numbers in the terminal window + hide_signcolumn = true, -- Hide the sign column in the terminal window + + -- Floating window configuration (only applies when position = "float") + float = { + width = "80%", -- Width: number of columns or percentage string + height = "80%", -- Height: number of rows or percentage string + row = "center", -- Row position: number, "center", or percentage string + col = "center", -- Column position: number, "center", or percentage string + relative = "editor", -- Relative to: "editor" or "cursor" + border = "rounded", -- Border style: "none", "single", "double", "rounded", "solid", "shadow" + }, + }, + -- File refresh settings + refresh = { + enable = true, -- Enable file change detection + updatetime = 100, -- updatetime when Claude Code is active (milliseconds) + timer_interval = 1000, -- How often to check for file changes (milliseconds) + show_notifications = true, -- Show notification when files are reloaded + }, + -- Git project settings + git = { + use_git_root = true, -- Set CWD to git root when opening Claude Code (if in git project) + }, + -- Shell-specific settings + shell = { + separator = "&&", -- Command separator used in shell commands + pushd_cmd = "pushd", -- Command to push directory onto stack (e.g., 'pushd' for bash/zsh, 'enter' for nushell) + popd_cmd = "popd", -- Command to pop directory from stack (e.g., 'popd' for bash/zsh, 'exit' for nushell) + }, + -- Command settings + command = "claude", -- Command used to launch Claude Code + -- Command variants + command_variants = { + -- Conversation management + continue = "--continue", -- Resume the most recent conversation + resume = "--resume", -- Display an interactive conversation picker + + -- Output options + verbose = "--verbose", -- Enable verbose logging with full turn-by-turn output + }, + -- Keymaps + keymaps = { + toggle = { + normal = "<C-,>", -- Normal mode keymap for toggling Claude Code, false to disable + terminal = "<C-,>", -- Terminal mode keymap for toggling Claude Code, false to disable + variants = { + continue = false, -- handled by lazy `keys` (<leader>aK) so which-key shows the description + verbose = false, -- handled by lazy `keys` (<leader>av) + }, + }, + window_navigation = true, -- Enable window navigation keymaps (<C-h/j/k/l>) + scrolling = true, -- Enable scrolling keymaps (<C-f/b>) for page up/down + }, + }) + end, +} diff --git a/nvim/lua/plugins/claude-code.lua b/nvim/lua/plugins/claude-code.lua index 206a7896..e0a9891f 100644 --- a/nvim/lua/plugins/claude-code.lua +++ b/nvim/lua/plugins/claude-code.lua @@ -1,7 +1,7 @@ return { { "coder/claudecode.nvim", - enabled = true, + enabled = false, opts = { terminal = { split_side = "right", @@ -18,7 +18,7 @@ return { { "<leader>ac", "<cmd>ClaudeCode<cr>", desc = "Toggle Claude" }, { "<leader>af", "<cmd>ClaudeCodeFocus<cr>", desc = "Focus Claude" }, { "<leader>ar", "<cmd>ClaudeCode --resume<cr>", desc = "Resume Claude" }, - { "<leader>aC", "<cmd>ClaudeCode --continue<cr>", desc = "Continue Claude" }, + { "<leader>aK", "<cmd>ClaudeCode --continue<cr>", desc = "Continue Claude" }, { "<leader>ab", "<cmd>ClaudeCodeAdd %<cr>", desc = "Add current buffer" }, { "<leader>as", "<cmd>ClaudeCodeSend<cr>", mode = "v", desc = "Send to Claude" }, { diff --git a/nvim/lua/plugins/copilot-chat.lua b/nvim/lua/plugins/copilot-chat.lua deleted file mode 100644 index e799b2a8..00000000 --- a/nvim/lua/plugins/copilot-chat.lua +++ /dev/null @@ -1,309 +0,0 @@ -local M = {} - --- Prompt picker using Telescope -function M.pick(type) - return function() - local actions = require("CopilotChat.actions") - require("CopilotChat.integrations.telescope").pick(actions[type .. "_actions"]()) - end -end - -return { - "CopilotC-Nvim/CopilotChat.nvim", - branch = "main", - cmd = "CopilotChat", - enabled = false, - dependencies = { - { "nvim-telescope/telescope.nvim" }, - { "nvim-lua/plenary.nvim" }, - }, - opts = function() - local user = vim.env.USER or "User" - user = user:sub(1, 1):upper() .. user:sub(2) - - -- Base commit prompt template - local commit_prompt = - "Take a deep breath and analyze the changes made in the git diff. Then, write a commit message for the %s with commitizen convention, only use lower-case letters. Output the full multi-line command starting with `git commit -m` ready to be pasted into the terminal. If there are references to filenames or the backtics in the commit message, escape them with backslashes. i.e. \\` text with backticks \\`" - - return { - auto_insert_mode = true, - question_header = " " .. user .. " ", - answer_header = " Copilot ", - error_header = "## Error ", - window = { - width = 0.4, - }, - -- Register custom contexts - contexts = { - pr_diff = { - description = "Get the diff between the current branch and target branch", - resolve = function() - -- Check if we're in a git repository - local is_git = vim.fn.system("git rev-parse --is-inside-work-tree 2>/dev/null") - if vim.v.shell_error ~= 0 then - return { { content = "Not in a git repository", filename = "error", filetype = "text" } } - end - - -- Get target branch (main/master/develop) - local target_branch = vim.fn - .system( - "git for-each-ref --format='%(refname:short)' refs/heads/ | grep -E '^(main|master|develop)' | head -n 1" - ) - :gsub("\n", "") - if vim.v.shell_error ~= 0 or target_branch == "" then - return { { content = "Failed to determine target branch", filename = "error", filetype = "text" } } - end - - -- Fetch the latest changes from the remote repository - local fetch_result = vim.fn.system("git fetch origin " .. target_branch .. " 2>&1") - if vim.v.shell_error ~= 0 then - return { - { content = "Failed to fetch from remote: " .. fetch_result, filename = "error", filetype = "text" }, - } - end - - -- Get current branch - local current_branch = vim.fn.system("git rev-parse --abbrev-ref HEAD 2>/dev/null"):gsub("\n", "") - if vim.v.shell_error ~= 0 or current_branch == "" then - return { { content = "Failed to get current branch", filename = "error", filetype = "text" } } - end - - -- Get the diff - local cmd = - string.format("git diff --no-color --no-ext-diff origin/%s...%s 2>&1", target_branch, current_branch) - local handle = io.popen(cmd) - if not handle then - return { { content = "Failed to execute git diff", filename = "error", filetype = "text" } } - end - - local result = handle:read("*a") - handle:close() - - -- If there's no diff, return a meaningful message - if not result or result == "" then - return { - { - content = "No changes found between current branch and " .. target_branch, - filename = "info", - filetype = "text", - }, - } - end - - return { - { - content = result, - filename = "pr_diff", - filetype = "diff", - }, - } - end, - }, - }, - -- Custom prompts incorporating git staged/unstaged functionality - prompts = { - -- Code related prompts - Explain = { - prompt = "Please explain how the following code works.", - system_prompt = "You are an expert software developer and teacher. Explain the code in a clear, concise way.", - }, - Review = { - prompt = "Please review the following code and provide suggestions for improvement.", - system_prompt = "You are an expert code reviewer. Focus on best practices, performance, and potential issues.", - }, - Tests = { - prompt = "Please explain how the selected code works, then generate unit tests for it.", - system_prompt = "You are an expert in software testing. Generate thorough test cases covering edge cases.", - }, - Refactor = { - prompt = "Please refactor the following code to improve its clarity and readability.", - system_prompt = "You are an expert in code refactoring. Focus on making the code more maintainable and easier to understand.", - }, - FixCode = { - prompt = "Please fix the following code to make it work as intended.", - system_prompt = "You are an expert programmer. Help fix code issues while maintaining code style and best practices.", - }, - FixError = { - prompt = "Please explain the error in the following text and provide a solution.", - system_prompt = "You are an expert in debugging. Help identify and fix the error while explaining the solution.", - }, - BetterNamings = { - prompt = "Please provide better names for the following variables and functions.", - system_prompt = "You are an expert in code readability. Suggest clear, descriptive names following naming conventions.", - }, - Documentation = { - prompt = "Please provide documentation for the following code.", - system_prompt = "You are an expert technical writer. Create clear, comprehensive documentation.", - }, - SwaggerApiDocs = { - prompt = "Please provide documentation for the following API using Swagger.", - system_prompt = "You are an expert in API documentation. Create comprehensive Swagger/OpenAPI documentation.", - }, - SwaggerJsDocs = { - prompt = "Please write JSDoc for the following API using Swagger.", - system_prompt = "You are an expert in JavaScript documentation. Create comprehensive JSDoc with Swagger annotations.", - }, - -- Git related prompts - Commit = { - prompt = "> #git:staged\n\n" .. string.format(commit_prompt, "change"), - system_prompt = "You are an expert in writing clear, concise git commit messages following best practices.", - }, - CommitStaged = { - prompt = "> #git:staged\n\n" .. string.format(commit_prompt, "staged changes"), - system_prompt = "You are an expert in writing clear, concise git commit messages following best practices.", - }, - CommitUnstaged = { - prompt = "> #git:unstaged\n\n" .. string.format(commit_prompt, "unstaged changes"), - system_prompt = "You are an expert in writing clear, concise git commit messages following best practices.", - }, - PullRequest = { - prompt = "> #pr_diff\n\nWrite a pull request description for these changes. Include a clear title, summary of changes, and any important notes.", - system_prompt = [[You are an experienced software engineer about to open a PR. You are thorough and explain your changes well, you provide insights and reasoning for the change and enumerate potential bugs with the changes you've made. - - Your task is to create a pull request for the given code changes. Follow these steps: - - 1. Analyze the git diff changes provided. - 2. Draft a comprehensive description of the pull request based on the input. - 3. Create the gh CLI command to create a GitHub pull request. - - Output Instructions: - - The command should start with `gh pr create`. - - Do not use the new line character in the command since it does not work - - Output needs to be a multi-line command - - Include the `--base $(git parent)` flag - - Use the `--title` flag with a concise, descriptive title matching the commitzen convention. - - Use the `--body` flag for the PR description. - - Include the following sections in the body: - - '## Summary' with a brief overview of changes - - '## Changes' listing specific modifications - - '## Additional Notes' for any extra information - - Escape any backticks in the message body to avoid shell interpretation issues - - Wrap the entire command in a code block for easy copy-pasting. - - Desired Output: - ```sh - gh pr create \ - --base $(git parent) \ - --title "feat: your title here" \ - --body "## Summary - Your summary here - - ## Changes - - Change 1 - - Change 2 - - Change 3 \`with backticks\` - - ## Additional Notes - Your notes here" - ```]], - }, - -- Text related prompts - Summarize = { - prompt = "Please summarize the following text.", - system_prompt = "You are an expert in technical writing. Create clear, concise summaries.", - }, - Spelling = { - prompt = "Please correct any grammar and spelling errors in the following text.", - system_prompt = "You are an expert editor. Fix grammar and spelling while maintaining the original meaning.", - }, - Wording = { - prompt = "Please improve the grammar and wording of the following text.", - system_prompt = "You are an expert writer. Improve clarity and readability while maintaining the original meaning.", - }, - Concise = { - prompt = "Please rewrite the following text to make it more concise.", - system_prompt = "You are an expert in technical writing. Make the text more concise while preserving key information.", - }, - }, - } - end, - keys = { - { "<c-s>", "<CR>", ft = "copilot-chat", desc = "Submit Prompt", remap = true }, - { "<leader>a", "", desc = "+ai", mode = { "n", "v" } }, - -- Toggle and clear - { - "<leader>aa", - function() - return require("CopilotChat").toggle() - end, - desc = "Toggle (CopilotChat)", - mode = { "n", "v" }, - }, - { - "<leader>ax", - function() - return require("CopilotChat").reset() - end, - desc = "Clear (CopilotChat)", - mode = { "n", "v" }, - }, - -- Quick chat - { - "<leader>aq", - function() - local input = vim.fn.input("Quick Chat: ") - if input ~= "" then - require("CopilotChat").ask(input) - end - end, - desc = "Quick Chat (CopilotChat)", - mode = { "n", "v" }, - }, - -- Show help and prompts with telescope - { "<leader>ah", M.pick("help"), desc = "Help Actions (CopilotChat)", mode = { "n", "v" } }, - { "<leader>ap", M.pick("prompt"), desc = "Prompt Actions (CopilotChat)", mode = { "n", "v" } }, - -- Code related commands - { "<leader>ae", "<cmd>CopilotChatExplain<cr>", desc = "Explain Code" }, - { "<leader>at", "<cmd>CopilotChatTests<cr>", desc = "Generate Tests" }, - { "<leader>ar", "<cmd>CopilotChatReview<cr>", desc = "Review Code" }, - { "<leader>aR", "<cmd>CopilotChatRefactor<cr>", desc = "Refactor Code" }, - { "<leader>an", "<cmd>CopilotChatBetterNamings<cr>", desc = "Better Naming" }, - -- Git related commands - { "<leader>ac", "<cmd>CopilotChatCommit<cr>", desc = "Generate Commit Message" }, - { "<leader>as", "<cmd>CopilotChatCommitStaged<cr>", desc = "Commit Staged Changes" }, - { "<leader>au", "<cmd>CopilotChatCommitUnstaged<cr>", desc = "Commit Unstaged Changes" }, - { "<leader>ap", "<cmd>CopilotChatPullRequest<cr>", desc = "Generate Pull Request" }, - -- Debug and fix - { "<leader>ad", "<cmd>CopilotChatDebugInfo<cr>", desc = "Debug Info" }, - { "<leader>af", "<cmd>CopilotChatFixDiagnostic<cr>", desc = "Fix Diagnostic" }, - -- Models - { "<leader>am", "<cmd>CopilotChatModels<cr>", desc = "Select Models" }, - }, - config = function(_, opts) - local chat = require("CopilotChat") - local select = require("CopilotChat.select") - - -- Disable line numbers in chat window - vim.api.nvim_create_autocmd("BufEnter", { - pattern = "copilot-chat", - callback = function() - vim.opt_local.relativenumber = false - vim.opt_local.number = false - end, - }) - - -- Setup CMP integration - require("CopilotChat.integrations.cmp").setup() - - -- Create commands for visual mode - vim.api.nvim_create_user_command("CopilotChatVisual", function(args) - chat.ask(args.args, { selection = select.visual }) - end, { nargs = "*", range = true }) - - -- Inline chat with Copilot - vim.api.nvim_create_user_command("CopilotChatInline", function(args) - chat.ask(args.args, { - selection = select.visual, - window = { - layout = "float", - relative = "cursor", - width = 1, - height = 0.4, - row = 1, - }, - }) - end, { nargs = "*", range = true }) - - chat.setup(opts) - end, -} diff --git a/nvim/lua/plugins/copilot.lua b/nvim/lua/plugins/copilot.lua deleted file mode 100644 index 4d1244dc..00000000 --- a/nvim/lua/plugins/copilot.lua +++ /dev/null @@ -1,25 +0,0 @@ -return { - "zbirenbaum/copilot.lua", - cmd = "Copilot", - build = ":Copilot auth", - enabled = false, - event = "InsertEnter", - opts = { - suggestion = { - enabled = not vim.g.ai_cmp, - auto_trigger = true, - keymap = { - accept = false, -- handled by nvim-cmp / blink.cmp - next = "<M-]>", - prev = "<M-[>", - }, - }, - panel = { enabled = false }, - filetypes = { - gitcommit = true, - markdown = true, - typescript = true, - yaml = true, - }, - }, -} diff --git a/nvim/lua/plugins/snacks.lua b/nvim/lua/plugins/snacks.lua index a72cfd1e..f7768db7 100644 --- a/nvim/lua/plugins/snacks.lua +++ b/nvim/lua/plugins/snacks.lua @@ -15,23 +15,60 @@ return { Snacks.gitbrowse() end, }, - function() + function(self) local in_git = Snacks.git.get_root() ~= nil local remote = in_git and vim.fn.system("git remote get-url origin 2>/dev/null"):gsub("%s+", "") or "" local is_github = remote:find("github%.com") ~= nil local is_gitlab = remote:find("gitlab") ~= nil or remote:find("git%.treatwell") ~= nil + local repo = remote:match("github%.com[:/]([^/]+/[^/]+)") + repo = repo and repo:gsub("%.git$", "") or "" + + -- Notifications as plain text (not a terminal section) so nothing renders + -- when empty and no "[Process exited 0]" can appear. Reads a cache file and + -- refreshes it in the background for the next dashboard open. + local items = {} + if in_git and is_github and repo ~= "" then + local notif_file = vim.fn.stdpath("cache") .. "/dash-gh-notify-" .. repo:gsub("/", "_") .. ".txt" + local stat = vim.uv.fs_stat(notif_file) + if not stat or os.time() - stat.mtime.sec > 300 then + local strip = [[perl -pe 's/\e\[[0-9;]*[A-Za-z]//g; s/\e\][^\a\e]*(?:\a|\e\\)//g; s/[\r\a]//g']] + vim.fn.jobstart({ + "sh", + "-c", + ("gh notify -s -a -n5 -f '%s' 2>/dev/null | grep -v 'No results found.' | %s > '%s'"):format( + repo, + strip, + notif_file + ), + }) + end + local lines = stat and vim.fn.readfile(notif_file) or {} + lines = vim.tbl_filter(function(l) + return l:match("%S") + end, lines) + if #lines > 0 then + items[#items + 1] = { + pane = 2, + icon = " ", + title = "Notifications", + key = "N", + action = function() + vim.ui.open("https://github.com/notifications?query=repo%3A" .. repo:gsub("/", "%%2F")) + end, + } + local width = (self and self.opts and self.opts.width or 60) - 3 + for i, l in ipairs(lines) do + items[#items + 1] = { + pane = 2, + indent = 3, + padding = i == #lines and 1 or 0, + text = { { vim.fn.strcharpart(l, 0, width), hl = "dir" } }, + } + end + end + end + local cmds = { - { - title = "Notifications", - cmd = "gh notify -s -a -n5", - action = function() - vim.ui.open("https://github.com/notifications") - end, - key = "N", - icon = " ", - height = 5, - enabled = is_github, - }, -- { -- title = "Open Issues", -- cmd = "gh issue list -L 3", @@ -65,8 +102,8 @@ return { enabled = is_gitlab, }, } - return vim.tbl_map(function(cmd) - return vim.tbl_extend("force", { + for _, cmd in ipairs(cmds) do + items[#items + 1] = vim.tbl_extend("force", { pane = 2, section = "terminal", enabled = in_git, @@ -74,7 +111,8 @@ return { ttl = 5 * 60, indent = 3, }, cmd) - end, cmds) + end + return items end, { section = "startup" }, },