diff --git a/README.md b/README.md index a38ec5f9..a5a73c0d 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ Personal macOS and Linux shell setup managed with [chezmoi](https://chezmoi.io/) ## What's in this repo - `chezmoi/`: templated dotfiles for shell and tool config (zsh, git, helix, wezterm, bat, etc.) +- `plugins/`: personal Claude Code plugins — a local marketplace installed via `chezmoi apply` - `bin/`: convenience maintenance scripts (`update-all.sh`, `cleanup-all.sh`) ## Apply directly from repo @@ -53,6 +54,22 @@ pre-commit install ./bin/cleanup-all.sh ``` +## Claude Code plugins + +Personal plugins in `plugins/` are served as a local marketplace. Registered by `chezmoi apply` +pointing directly at this directory — no deployment step, edits take effect immediately. + +Current plugins: + +- `okf-wiki` — SessionEnd hook that distills memsearch journals into the `~/wiki` OKF bundle + +To install without cloning the repo: + +```bash +claude plugin marketplace add "https://github.com/yxtay/dotfiles" --sparse plugins +claude plugin install okf-wiki@yxtay +``` + ## License MIT diff --git a/chezmoi/.chezmoiscripts/run_onchange_after_agent_plugins.sh.tmpl b/chezmoi/.chezmoiscripts/run_onchange_after_agent_plugins.sh.tmpl index dd0d0b0e..8229179b 100644 --- a/chezmoi/.chezmoiscripts/run_onchange_after_agent_plugins.sh.tmpl +++ b/chezmoi/.chezmoiscripts/run_onchange_after_agent_plugins.sh.tmpl @@ -29,7 +29,7 @@ agents+=(claude-code) {{- end }} repo_skills=( - mattpocock/skills=wayfinder,grill-with-docs,to-spec,to-tickets,implement,code-review,diagnosing-bugs + mattpocock/skills=wayfinder,grill-with-docs,to-spec,to-tickets,implement,diagnosing-bugs mattpocock/skills=grilling,domain-modeling,research,prototype,codebase-design,tdd,triage,handoff mattpocock/skills=setup-matt-pocock-skills,improve-codebase-architecture,writing-great-skills philschmid/mcp-cli=mcp-cli diff --git a/chezmoi/.chezmoiscripts/run_onchange_after_setup_claude.sh.tmpl b/chezmoi/.chezmoiscripts/run_onchange_after_setup_claude.sh.tmpl index 0c231ace..ad62eb44 100644 --- a/chezmoi/.chezmoiscripts/run_onchange_after_setup_claude.sh.tmpl +++ b/chezmoi/.chezmoiscripts/run_onchange_after_setup_claude.sh.tmpl @@ -16,8 +16,8 @@ for entry in "${marketplaces[@]}"; do claude plugin marketplace add "${entry}" done -# Local marketplace — personal plugins in ~/.agents/plugins/ -claude plugin marketplace add "${HOME}/.agents/plugins" +# Local marketplace — personal plugins in this repo, installed directly. +claude plugin marketplace add "{{ .chezmoi.sourceDir }}/../plugins" plugins=( claude-code-setup@claude-plugins-official diff --git a/chezmoi/.chezmoitemplates/claude-settings.json b/chezmoi/.chezmoitemplates/claude-settings.json index 09b0e002..b49244cd 100644 --- a/chezmoi/.chezmoitemplates/claude-settings.json +++ b/chezmoi/.chezmoitemplates/claude-settings.json @@ -22,6 +22,7 @@ "permissions": { "defaultMode": "auto", "disableBypassPermissionsMode": "disable", + "additionalDirectories": ["~/.memsearch", "~/wiki"], "ask": [ "Edit(**/.env)", "Edit(**/.env.*)", @@ -56,7 +57,6 @@ "excludedCommands": ["docker *"], "filesystem": { "allowWrite": [ - "./.git/config*", "~/.cache", "~/.cargo/registry", "~/.gradle", diff --git a/chezmoi/private_dot_agents/plugins/okf-wiki/hooks/okf-wiki-maintenance.sh b/chezmoi/private_dot_agents/plugins/okf-wiki/hooks/okf-wiki-maintenance.sh deleted file mode 100644 index 8a956e7f..00000000 --- a/chezmoi/private_dot_agents/plugins/okf-wiki/hooks/okf-wiki-maintenance.sh +++ /dev/null @@ -1,81 +0,0 @@ -#!/usr/bin/env bash -# SessionEnd / PreCompact hook: periodically distill memsearch journals into the ~/wiki OKF bundle. -# Independent of the memsearch plugin's own SessionEnd hook — reads its journal output -# as a data source only, no changes to memsearch itself. -set -euo pipefail - -# Avoid recursing into our own hooks when the nested `claude -p` below exits. -if [ "${MEMSEARCH_DISABLE:-}" = "1" ] || [ "${OKF_WIKI_DISABLE:-}" = "1" ]; then - exit 0 -fi - -MIN_INTERVAL_HOURS="${OKF_WIKI_MIN_INTERVAL_HOURS:-24}" -MEMSEARCH_DIR="${MEMSEARCH_DIR:-$HOME/.memsearch}" -JOURNAL_DIR="$MEMSEARCH_DIR/memory" -WIKI_DIR="${OKF_WIKI_DIR:-$HOME/wiki}" -STATE_FILE="$WIKI_DIR/.okf-wiki-last-run" -LOCK_FILE="$WIKI_DIR/.okf-wiki-maintenance.lock" -PROMPT_FILE="${CLAUDE_PLUGIN_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}/okf-wiki-review.txt" - -[ -d "$JOURNAL_DIR" ] || exit 0 -mkdir -p "$WIKI_DIR" -command -v claude &>/dev/null || exit 0 - -# Acquire exclusive lock — prevents concurrent SessionEnd + PreCompact runs. -# fd 9 is opened on LOCK_FILE; flock holds it for this process's lifetime. -exec 9>"$LOCK_FILE" -flock -n 9 || exit 0 # -n = non-blocking: exit immediately if already locked - -# Skip if last run was within MIN_INTERVAL_HOURS. -if [ -f "$STATE_FILE" ]; then - last_run="$(cat "$STATE_FILE" 2>/dev/null)" - # Guard: treat empty or non-numeric state file as 0 (never run). - if ! [[ "$last_run" =~ ^[0-9]+$ ]]; then last_run=0; fi - now="$(date +%s)" - elapsed_hours=$(((now - last_run) / 3600)) - if [ "$elapsed_hours" -lt "$MIN_INTERVAL_HOURS" ]; then - exit 0 - fi -fi - -# Only look at journals touched since the last run (falls back to last 3 days on first run). -if [ -f "$STATE_FILE" ]; then - recent_journals="$(find "$JOURNAL_DIR" -maxdepth 1 -name '*.md' -newer "$STATE_FILE" 2>/dev/null)" -else - recent_journals="$(find "$JOURNAL_DIR" -maxdepth 1 -name '*.md' -mtime -3 2>/dev/null)" -fi - -[ -n "$recent_journals" ] || exit 0 - -# Include memsearch-synthesized summaries if present (higher-signal than raw journals). -extra_context="" -[ -f "$MEMSEARCH_DIR/PROJECT.md" ] && extra_context="$extra_context\nProject summary: $MEMSEARCH_DIR/PROJECT.md" -[ -f "$MEMSEARCH_DIR/USER.md" ] && extra_context="$extra_context\nUser profile: $MEMSEARCH_DIR/USER.md" - -export MEMSEARCH_DISABLE=1 -export MEMSEARCH_NO_WATCH=1 -export OKF_WIKI_DISABLE=1 -export CLAUDECODE= # clear CLAUDECODE so the child session doesn't inherit hook context - -# hook runs with async:true — Claude Code does not block on this script. -# Run claude -p synchronously so flock holds for the full duration, preventing -# a concurrent PreCompact trigger from starting a second run. -# Write state file only on success so a failed run does not suppress the next. -if claude -p \ - --strict-mcp-config \ - --no-session-persistence \ - --allowed-tools "Skill,Read,Write,Edit,Glob,Grep" \ - --add-dir "$WIKI_DIR" \ - --add-dir "$JOURNAL_DIR" \ - --add-dir "$MEMSEARCH_DIR" \ - --system-prompt "$(cat "$PROMPT_FILE")" \ - "Journal directory: $JOURNAL_DIR -Wiki directory: $WIKI_DIR -Recently changed journal files: -$recent_journals${extra_context:+ -$extra_context}" \ - >/dev/null 2>&1; then - date +%s >"$STATE_FILE" -fi - -exit 0 diff --git a/chezmoi/private_dot_agents/plugins/dot_claude-plugin/marketplace.json b/plugins/.claude-plugin/marketplace.json similarity index 100% rename from chezmoi/private_dot_agents/plugins/dot_claude-plugin/marketplace.json rename to plugins/.claude-plugin/marketplace.json diff --git a/chezmoi/private_dot_agents/plugins/okf-wiki/dot_claude-plugin/plugin.json b/plugins/okf-wiki/.claude-plugin/plugin.json similarity index 100% rename from chezmoi/private_dot_agents/plugins/okf-wiki/dot_claude-plugin/plugin.json rename to plugins/okf-wiki/.claude-plugin/plugin.json diff --git a/chezmoi/private_dot_agents/plugins/okf-wiki/hooks/hooks.json b/plugins/okf-wiki/hooks/hooks.json similarity index 100% rename from chezmoi/private_dot_agents/plugins/okf-wiki/hooks/hooks.json rename to plugins/okf-wiki/hooks/hooks.json diff --git a/plugins/okf-wiki/hooks/okf-wiki-maintenance.sh b/plugins/okf-wiki/hooks/okf-wiki-maintenance.sh new file mode 100755 index 00000000..b6cc4a0a --- /dev/null +++ b/plugins/okf-wiki/hooks/okf-wiki-maintenance.sh @@ -0,0 +1,79 @@ +#!/usr/bin/env bash +# SessionEnd / PreCompact hook: periodically distill memsearch journals into the ~/wiki OKF bundle. +# Independent of the memsearch plugin's own SessionEnd hook — reads its journal output +# as a data source only, no changes to memsearch itself. +set -euo pipefail + +# Avoid recursing into our own hooks when the nested `claude -p` below exits. +if [ "${MEMSEARCH_DISABLE:-}" = "1" ] || [ "${OKF_WIKI_DISABLE:-}" = "1" ]; then + exit 0 +fi + +MEMSEARCH_DIR="${MEMSEARCH_DIR:-${HOME}/.memsearch}" +JOURNAL_DIR="${MEMSEARCH_DIR}/memory" +WIKI_DIR="${OKF_WIKI_DIR:-${HOME}/wiki}" +STATE_FILE="${WIKI_DIR}/.okf-wiki-last-run" +LOCK_FILE="${WIKI_DIR}/.okf-wiki-maintenance.lock" +PROMPT_FILE="${CLAUDE_PLUGIN_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}/prompts/okf-wiki-review.txt" + +[ -d "${JOURNAL_DIR}" ] || exit 0 +[ -f "${PROMPT_FILE}" ] || exit 0 +mkdir -p "${WIKI_DIR}" +command -v claude &>/dev/null || exit 0 + +# Acquire exclusive lock — prevents concurrent SessionEnd + PreCompact runs. +# fd 9 is opened on LOCK_FILE; flock holds it for this process's lifetime. +exec 9>"${LOCK_FILE}" +flock -n 9 || exit 0 # -n = non-blocking: exit immediately if already locked + +# Skip if already ran today (same calendar date). +if [ -f "${STATE_FILE}" ]; then + last_run_date="$(cat "${STATE_FILE}" 2>/dev/null)" + today="$(date +%Y-%m-%d)" + if [ "${last_run_date}" = "${today}" ]; then + exit 0 + fi +fi + +# Only look at journals touched since the last run (falls back to last 3 days on first run). +if [ -f "${STATE_FILE}" ]; then + recent_journals="$(find "${JOURNAL_DIR}" -maxdepth 1 -name '*.md' -newer "${STATE_FILE}" 2>/dev/null)" +else + recent_journals="$(find "${JOURNAL_DIR}" -maxdepth 1 -name '*.md' -mtime -3 2>/dev/null)" +fi + +[ -n "${recent_journals}" ] || exit 0 + +# Include memsearch-synthesized summaries if present (higher-signal than raw journals). +extra_context="" +[ -f "${MEMSEARCH_DIR}/PROJECT.md" ] && extra_context="${extra_context} +Project summary: ${MEMSEARCH_DIR}/PROJECT.md" +[ -f "${MEMSEARCH_DIR}/USER.md" ] && extra_context="${extra_context} +User profile: ${MEMSEARCH_DIR}/USER.md" + +export MEMSEARCH_DISABLE=1 +export MEMSEARCH_NO_WATCH=1 +export OKF_WIKI_DISABLE=1 +unset CLAUDECODE # clear CLAUDECODE so the child session doesn't inherit hook context + +# hook runs with async:true — Claude Code does not block on this script. +# Run claude -p synchronously so flock holds for the full duration, preventing +# a concurrent PreCompact trigger from starting a second run. +# Write state file only on success so a failed run does not suppress the next. +if claude -p \ + --strict-mcp-config \ + --no-session-persistence \ + --model haiku \ + --effort low \ + --allowed-tools "Skill,Read,Write,Edit,Glob,Grep" \ + --append-system-prompt-file "${PROMPT_FILE}" \ + --add-dir "${WIKI_DIR}" \ + --add-dir "${MEMSEARCH_DIR}" \ + "Wiki directory: ${WIKI_DIR} +Recently changed journal files: +${recent_journals}${extra_context}" \ + >/dev/null 2>&1; then + date +%Y-%m-%d >"${STATE_FILE}" +fi + +exit 0 diff --git a/chezmoi/private_dot_agents/plugins/okf-wiki/okf-wiki-review.txt b/plugins/okf-wiki/prompts/okf-wiki-review.txt similarity index 95% rename from chezmoi/private_dot_agents/plugins/okf-wiki/okf-wiki-review.txt rename to plugins/okf-wiki/prompts/okf-wiki-review.txt index f63319e6..c88875c4 100644 --- a/chezmoi/private_dot_agents/plugins/okf-wiki/okf-wiki-review.txt +++ b/plugins/okf-wiki/prompts/okf-wiki-review.txt @@ -1,8 +1,5 @@ You are maintaining a personal OKF (Open Knowledge Format) wiki. -Wiki directory: provided in the user message -Journal directory: provided in the user message - You will receive recently-changed memsearch journal files (daily markdown logs of past coding sessions) and, when listed, memsearch-synthesized PROJECT.md and USER.md summaries as additional context. Prefer the synthesized summaries as higher-signal; use raw journals for detail not