@slamb2k/mad-skills 2.0.6 → 2.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "slamb2k-plugins",
3
+ "description": "Claude Code plugins by Simon Lamb",
4
+ "version": "1.2.0",
5
+ "owner": {
6
+ "name": "Simon Lamb",
7
+ "url": "https://github.com/slamb2k"
8
+ },
9
+ "plugins": [
10
+ {
11
+ "name": "mad-skills",
12
+ "description": "AI-assisted planning, development and governance tools",
13
+ "version": "2.0.7",
14
+ "author": {
15
+ "name": "slamb2k",
16
+ "url": "https://github.com/slamb2k"
17
+ },
18
+ "source": "./",
19
+ "category": "development",
20
+ "homepage": "https://github.com/slamb2k/mad-skills",
21
+ "tags": ["planning", "tdd", "architecture", "llm-review", "implementation"]
22
+ }
23
+ ]
24
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "mad-skills",
3
+ "description": "AI-assisted planning, development and governance tools",
4
+ "version": "2.0.7",
5
+ "author": {
6
+ "name": "slamb2k",
7
+ "url": "https://github.com/slamb2k"
8
+ },
9
+ "repository": "https://github.com/slamb2k/mad-skills",
10
+ "license": "MIT",
11
+ "keywords": ["planning", "tdd", "architecture", "llm-review", "implementation"],
12
+ "commands": ["./commands/"],
13
+ "agents": ["./agents/"],
14
+ "skills": ["./skills/"],
15
+ "hooks": "./hooks/"
16
+ }
@@ -0,0 +1,91 @@
1
+ ---
2
+ name: ship-analyzer
3
+ description: >
4
+ Analyzes working tree changes, creates semantic commits with well-crafted messages,
5
+ pushes to a feature branch, and creates a detailed pull request. Use this agent for
6
+ the commit+push+PR phase of shipping code. It reads diffs and source files to
7
+ understand what changed and why, producing high-quality commit messages and PR
8
+ descriptions that a Bash-only agent cannot.
9
+ model: sonnet
10
+ ---
11
+
12
+ You are a senior engineer responsible for crafting high-quality git commits and pull requests. You read and understand code — not just diffs — to produce meaningful, accurate descriptions of what changed and why.
13
+
14
+ ## Core Principles
15
+
16
+ 1. **Read before writing** — Always read the actual diff AND relevant source files before composing commit messages. Never guess at intent from filenames alone.
17
+ 2. **Semantic grouping** — Group related changes into logical commits. A "logical group" shares a single purpose (e.g., all security changes together, all test updates together).
18
+ 3. **Concise but complete** — Commit messages explain WHAT and WHY in 1-2 sentences. PR descriptions give the full picture.
19
+ 4. **No attribution lines** — Never add Co-Authored-By, Generated-by, or similar lines to commits.
20
+
21
+ ## Commit Message Format
22
+
23
+ ```
24
+ <type>(<scope>): <imperative description>
25
+
26
+ <optional body: what changed and why, wrapped at 72 chars>
27
+ ```
28
+
29
+ Types: `feat`, `fix`, `refactor`, `docs`, `chore`, `test`, `perf`
30
+
31
+ Examples of GOOD messages:
32
+ - `feat(auth): replace pairing gate with channel allowlist`
33
+ - `fix(memory): correct positional arg order in get_recent_commitments`
34
+ - `refactor(workspace): collapse per-user directories to single workspace`
35
+ - `test(commitments): update calls for keyword-arg signatures`
36
+
37
+ Examples of BAD messages:
38
+ - `update files` (too vague)
39
+ - `feat: changes to auth system` (no scope, vague description)
40
+ - `fix various issues across the codebase` (multiple concerns in one)
41
+
42
+ ## PR Description Format
43
+
44
+ ```markdown
45
+ ## Summary
46
+ <1-3 sentences: what this PR accomplishes and why>
47
+
48
+ ## Changes
49
+ <bullet list of key changes, grouped logically>
50
+
51
+ ## Testing
52
+ - [ ] <specific verification steps>
53
+ ```
54
+
55
+ Keep the PR title under 72 characters. Use the same `<type>: <description>` format.
56
+
57
+ ## Workflow
58
+
59
+ When given a set of files to ship:
60
+
61
+ 1. **Understand the changes**
62
+ - Run `git diff` and `git diff --cached` to see all changes
63
+ - Read source files where the diff alone doesn't explain intent
64
+ - Identify the logical groupings
65
+
66
+ 2. **Create branch** (if on main)
67
+ - Derive a semantic branch name from the changes: `feature/`, `fix/`, `refactor/`, `docs/`, `chore/`
68
+
69
+ 3. **Commit in logical groups**
70
+ - Stage specific files per group with `git add <files>`
71
+ - Write a commit message using the format above
72
+ - Use HEREDOC for multi-line messages
73
+
74
+ 4. **Push**
75
+ - `git push -u origin <branch>`
76
+
77
+ 5. **Create PR**
78
+ - Read the full diff against main to write the PR description
79
+ - Detect platform from remote URL (github.com → GitHub, dev.azure.com/visualstudio.com → Azure DevOps)
80
+ - GitHub: Use `gh pr create` with HEREDOC body
81
+ - Azure DevOps: Use `az repos pr create --title "..." --description "..." --source-branch <branch> --target-branch <default> --output json`
82
+
83
+ 6. **Report results** in the structured format requested by the caller
84
+
85
+ ## Important Rules
86
+
87
+ - If the caller specifies which files to include, respect that exactly — do not add extra files
88
+ - If the caller provides context about the changes (e.g., "single-tenant simplification"), use that to inform your descriptions
89
+ - When changes span many files, prioritize reading the most impactful diffs (source > tests > config)
90
+ - Use `git add -p` when only some hunks in a file should be in a given commit
91
+ - Always verify the branch pushed successfully before creating the PR
@@ -0,0 +1,9 @@
1
+ ---
2
+ description: Initialize project with GOTCHA/BRACE framework
3
+ argument-hint: [--no-brace] [--force]
4
+ allowed-tools: Bash, Read, Write, Edit, Glob, Grep, AskUserQuestion
5
+ ---
6
+
7
+ Flags: $ARGUMENTS
8
+
9
+ Follow instructions in: ~/.claude/skills/brace/instructions.md
@@ -0,0 +1,9 @@
1
+ ---
2
+ description: Context-isolated feature development pipeline
3
+ argument-hint: <detailed design/plan to implement> [--skip-questions] [--skip-review] [--no-ship] [--parallel-impl]
4
+ allowed-tools: Bash, Read, Write, Edit, Glob, Grep, AskUserQuestion
5
+ ---
6
+
7
+ Arguments: $ARGUMENTS
8
+
9
+ Follow instructions in: ~/.claude/skills/build/instructions.md
@@ -0,0 +1,9 @@
1
+ ---
2
+ description: Generate multiple unique web design variations for any website or web application
3
+ argument-hint: <count> --port <port> [--spec <path>] [--favorites <1,2,3>]
4
+ allowed-tools: Bash, Read, Write, Edit, Glob, Grep, WebFetch
5
+ ---
6
+
7
+ Arguments: $ARGUMENTS
8
+
9
+ Follow instructions in: ~/.claude/skills/distil/instructions.md
@@ -0,0 +1,9 @@
1
+ ---
2
+ description: Load project context for informed decisions
3
+ argument-hint: security, routing, adhd, dashboard, office, memory, tasks, channels (comma-separated)
4
+ allowed-tools: Read, Glob, Grep, LS
5
+ ---
6
+
7
+ Domain argument: $ARGUMENTS
8
+
9
+ Follow instructions in: ~/.claude/skills/prime/instructions.md
@@ -0,0 +1,9 @@
1
+ ---
2
+ description: Bootstrap repo with standard dev tools - lefthook, commit templates, PR templates, CI
3
+ argument-hint: --skip-system-check (optional)
4
+ allowed-tools: Bash, Read, Write, Edit, Glob, Grep, AskUserQuestion
5
+ ---
6
+
7
+ Flags: $ARGUMENTS
8
+
9
+ Follow instructions in: ~/.claude/skills/rig/instructions.md
@@ -0,0 +1,9 @@
1
+ ---
2
+ description: Ship changes - commit, push, create PR, wait for checks, merge, cleanup
3
+ argument-hint: --pr-only, --no-squash, --keep-branch (optional flags)
4
+ allowed-tools: Bash, Read, Glob, Grep, Skill
5
+ ---
6
+
7
+ Flags: $ARGUMENTS
8
+
9
+ Follow instructions in: ~/.claude/skills/ship/instructions.md
@@ -0,0 +1,9 @@
1
+ ---
2
+ description: Sync with origin/main, stash/restore changes, clean up stale branches
3
+ argument-hint: --no-stash, --no-cleanup, --no-rebase (optional flags)
4
+ allowed-tools: Bash
5
+ ---
6
+
7
+ Flags: $ARGUMENTS
8
+
9
+ Follow instructions in: ~/.claude/skills/sync/instructions.md
@@ -0,0 +1,402 @@
1
+ #!/usr/bin/env bash
2
+ # session-guard.sh — Claude Code SessionStart hook
3
+ # Validates Git repo, CLAUDE.md existence/freshness, and checks for staleness.
4
+ #
5
+ # Install: Add to ~/.claude/settings.json or .claude/settings.json:
6
+ # {
7
+ # "hooks": {
8
+ # "SessionStart": [
9
+ # {
10
+ # "hooks": [
11
+ # {
12
+ # "type": "command",
13
+ # "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/session-guard.sh"
14
+ # }
15
+ # ]
16
+ # }
17
+ # ]
18
+ # }
19
+ # }
20
+
21
+ # NOTE: We intentionally avoid `set -e` here. Many commands (grep, git, find,
22
+ # jq, stat) return non-zero for perfectly normal reasons (no matches, not a
23
+ # repo, missing files). Letting them fail gracefully is simpler and more
24
+ # reliable than wrapping every line in `|| true`.
25
+ set -uo pipefail
26
+
27
+ # ---------------------------------------------------------------------------
28
+ # Config
29
+ # ---------------------------------------------------------------------------
30
+ PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$(pwd)}"
31
+ CLAUDE_MD="$PROJECT_DIR/CLAUDE.md"
32
+ NOW=$(date +%s)
33
+ STALENESS_THRESHOLD=3 # Accumulated score >= this triggers user prompt
34
+
35
+ # ---------------------------------------------------------------------------
36
+ # State
37
+ # ---------------------------------------------------------------------------
38
+ EARLY_CONTEXT_PARTS=() # Git/CLAUDE.md warnings (emitted before staleness)
39
+ STALENESS_SIGNALS=()
40
+ STALENESS_SCORE=0
41
+
42
+ add_staleness() {
43
+ local msg="$1"
44
+ local weight="${2:-1}"
45
+ STALENESS_SIGNALS+=("⚠ $msg")
46
+ STALENESS_SCORE=$((STALENESS_SCORE + weight))
47
+ }
48
+
49
+ # Portable stat wrapper: returns mtime as epoch seconds
50
+ file_mtime() {
51
+ stat -c %Y "$1" 2>/dev/null || stat -f %m "$1" 2>/dev/null || echo "$NOW"
52
+ }
53
+
54
+ # ---------------------------------------------------------------------------
55
+ # 0) Git repository check
56
+ # ---------------------------------------------------------------------------
57
+ GIT_ROOT=""
58
+
59
+ if command -v git &>/dev/null; then
60
+ GIT_ROOT=$(git -C "$PROJECT_DIR" rev-parse --show-toplevel 2>/dev/null) || true
61
+
62
+ if [[ -z "$GIT_ROOT" ]]; then
63
+ # ---- Not inside any git repository ----------------------------------
64
+ EARLY_CONTEXT_PARTS+=("[SESSION GUARD] ⚠️ This directory is NOT tracked by Git.")
65
+ EARLY_CONTEXT_PARTS+=("")
66
+ EARLY_CONTEXT_PARTS+=("Please ask the user:")
67
+ EARLY_CONTEXT_PARTS+=("")
68
+ EARLY_CONTEXT_PARTS+=("\"This directory isn't inside a Git repository. Would you like me to:")
69
+ EARLY_CONTEXT_PARTS+=("")
70
+ EARLY_CONTEXT_PARTS+=("1. **Initialise Git** — I'll run \`git init\` to start tracking this project")
71
+ EARLY_CONTEXT_PARTS+=("2. **Skip** — Continue without version control")
72
+ EARLY_CONTEXT_PARTS+=("")
73
+ EARLY_CONTEXT_PARTS+=("What would you prefer?\"")
74
+ EARLY_CONTEXT_PARTS+=("")
75
+ EARLY_CONTEXT_PARTS+=("If the user chooses to initialise, run \`git init\` in the project directory, then suggest creating a .gitignore if one doesn't exist.")
76
+ EARLY_CONTEXT_PARTS+=("If the user chooses to skip, continue normally.")
77
+ EARLY_CONTEXT_PARTS+=("")
78
+
79
+ elif [[ "$GIT_ROOT" != "$PROJECT_DIR" ]]; then
80
+ # ---- Git root is an ancestor folder ---------------------------------
81
+ DEPTH=0
82
+ CHECK_DIR="$PROJECT_DIR"
83
+ while [[ "$CHECK_DIR" != "$GIT_ROOT" && "$CHECK_DIR" != "/" ]]; do
84
+ CHECK_DIR=$(dirname "$CHECK_DIR")
85
+ DEPTH=$((DEPTH + 1))
86
+ done
87
+
88
+ # Gather monorepo signals at the git root
89
+ MONOREPO_SIGNALS=()
90
+
91
+ # Workspace configs
92
+ if [[ -f "$GIT_ROOT/package.json" ]] && command -v jq &>/dev/null; then
93
+ if jq -e '.workspaces // empty' "$GIT_ROOT/package.json" &>/dev/null; then
94
+ MONOREPO_SIGNALS+=("package.json has 'workspaces' field")
95
+ fi
96
+ fi
97
+ [[ -f "$GIT_ROOT/pnpm-workspace.yaml" ]] && MONOREPO_SIGNALS+=("pnpm-workspace.yaml exists")
98
+ [[ -f "$GIT_ROOT/lerna.json" ]] && MONOREPO_SIGNALS+=("lerna.json exists")
99
+ [[ -f "$GIT_ROOT/nx.json" ]] && MONOREPO_SIGNALS+=("nx.json exists")
100
+ [[ -f "$GIT_ROOT/turbo.json" ]] && MONOREPO_SIGNALS+=("turbo.json exists")
101
+ [[ -f "$GIT_ROOT/rush.json" ]] && MONOREPO_SIGNALS+=("rush.json exists")
102
+
103
+ # Common monorepo directory patterns
104
+ for d in packages apps services libs modules projects; do
105
+ [[ -d "$GIT_ROOT/$d" ]] && MONOREPO_SIGNALS+=("'$d/' directory exists at git root")
106
+ done
107
+
108
+ # Multiple package.json files (strong monorepo indicator)
109
+ PKG_COUNT=$(find "$GIT_ROOT" -maxdepth 3 -name "package.json" -not -path "*/node_modules/*" 2>/dev/null | wc -l | tr -d ' ')
110
+ (( PKG_COUNT > 2 )) && MONOREPO_SIGNALS+=("${PKG_COUNT} package.json files found within the repo")
111
+
112
+ # Multiple CLAUDE.md files (intentional per-package setup)
113
+ CLAUDE_MD_COUNT=$(find "$GIT_ROOT" -maxdepth 3 -name "CLAUDE.md" 2>/dev/null | wc -l | tr -d ' ')
114
+ (( CLAUDE_MD_COUNT > 1 )) && MONOREPO_SIGNALS+=("${CLAUDE_MD_COUNT} CLAUDE.md files found (suggests per-package setup)")
115
+
116
+ RELATIVE_PATH="${PROJECT_DIR#"$GIT_ROOT/"}"
117
+
118
+ if (( ${#MONOREPO_SIGNALS[@]} >= 2 )); then
119
+ # ---- Likely a legitimate monorepo ---------------------------------
120
+ EARLY_CONTEXT_PARTS+=("[SESSION GUARD] ℹ️ Git root is ${DEPTH} level(s) above the current directory.")
121
+ EARLY_CONTEXT_PARTS+=(" Git root: $GIT_ROOT")
122
+ EARLY_CONTEXT_PARTS+=(" Working dir: $PROJECT_DIR")
123
+ EARLY_CONTEXT_PARTS+=("")
124
+ EARLY_CONTEXT_PARTS+=("This appears to be a **monorepo** based on these signals:")
125
+ for sig in "${MONOREPO_SIGNALS[@]}"; do
126
+ EARLY_CONTEXT_PARTS+=(" • $sig")
127
+ done
128
+ EARLY_CONTEXT_PARTS+=("")
129
+ EARLY_CONTEXT_PARTS+=("Briefly let the user know:")
130
+ EARLY_CONTEXT_PARTS+=("")
131
+ EARLY_CONTEXT_PARTS+=("\"I notice the Git repository root is at \`$GIT_ROOT\`, which looks like a monorepo. I'm working in the \`${RELATIVE_PATH}\` package. Just confirming — is this the right context, or did you mean to open Claude Code at the repo root?\"")
132
+ EARLY_CONTEXT_PARTS+=("")
133
+ EARLY_CONTEXT_PARTS+=("This is a low-priority confirmation — don't block on it. Continue with the session regardless.")
134
+ EARLY_CONTEXT_PARTS+=("")
135
+
136
+ else
137
+ # ---- Ancestor may be incorrectly initialised ----------------------
138
+ GIT_ROOT_FILE_COUNT=$(find "$GIT_ROOT" -maxdepth 1 -not -name '.*' 2>/dev/null | wc -l | tr -d ' ')
139
+
140
+ EARLY_CONTEXT_PARTS+=("[SESSION GUARD] ⚠️ Git root is ${DEPTH} level(s) above the current directory and does NOT look like a monorepo.")
141
+ EARLY_CONTEXT_PARTS+=(" Git root: $GIT_ROOT")
142
+ EARLY_CONTEXT_PARTS+=(" Working dir: $PROJECT_DIR")
143
+ EARLY_CONTEXT_PARTS+=(" Files at git root: ~${GIT_ROOT_FILE_COUNT}")
144
+ EARLY_CONTEXT_PARTS+=("")
145
+
146
+ if (( ${#MONOREPO_SIGNALS[@]} > 0 )); then
147
+ EARLY_CONTEXT_PARTS+=("Weak signals found (not enough for monorepo classification):")
148
+ for sig in "${MONOREPO_SIGNALS[@]}"; do
149
+ EARLY_CONTEXT_PARTS+=(" • $sig")
150
+ done
151
+ EARLY_CONTEXT_PARTS+=("")
152
+ fi
153
+
154
+ EARLY_CONTEXT_PARTS+=("This may indicate that an ancestor directory was accidentally initialised with \`git init\`.")
155
+ EARLY_CONTEXT_PARTS+=("")
156
+ EARLY_CONTEXT_PARTS+=("Please ask the user:")
157
+ EARLY_CONTEXT_PARTS+=("")
158
+ EARLY_CONTEXT_PARTS+=("\"I notice the Git repository root is at \`$GIT_ROOT\`, which is ${DEPTH} level(s) above your current working directory. This doesn't look like a monorepo setup, so the Git repo at that level may have been created accidentally.")
159
+ EARLY_CONTEXT_PARTS+=("")
160
+ EARLY_CONTEXT_PARTS+=("A few options:")
161
+ EARLY_CONTEXT_PARTS+=("1. **It's correct** — The repo root at \`$GIT_ROOT\` is intentional, carry on")
162
+ EARLY_CONTEXT_PARTS+=("2. **Initialise here instead** — I'll run \`git init\` in this directory to create a separate repo")
163
+ EARLY_CONTEXT_PARTS+=("3. **Investigate** — I'll look at the git root structure to help you decide")
164
+ EARLY_CONTEXT_PARTS+=("")
165
+ EARLY_CONTEXT_PARTS+=("What would you prefer?\"")
166
+ EARLY_CONTEXT_PARTS+=("")
167
+ EARLY_CONTEXT_PARTS+=("If 'correct': continue normally.")
168
+ EARLY_CONTEXT_PARTS+=("If 'initialise here': run \`git init\` in the project directory. Warn the user that the ancestor .git will still exist and they may want to remove it later.")
169
+ EARLY_CONTEXT_PARTS+=("If 'investigate': list the git root contents and recent commits to help the user understand what's tracked.")
170
+ EARLY_CONTEXT_PARTS+=("")
171
+ fi
172
+ fi
173
+ # else: GIT_ROOT == PROJECT_DIR — all good, no action needed
174
+ fi
175
+
176
+ # ---------------------------------------------------------------------------
177
+ # 1) CLAUDE.md existence check
178
+ # ---------------------------------------------------------------------------
179
+ if [[ ! -f "$CLAUDE_MD" ]]; then
180
+ EARLY_CONTEXT_PARTS+=("[SESSION GUARD] No CLAUDE.md was found in the project root.")
181
+ EARLY_CONTEXT_PARTS+=("")
182
+ EARLY_CONTEXT_PARTS+=("This directory has not been initialised for Claude Code. Please ask the user:")
183
+ EARLY_CONTEXT_PARTS+=("")
184
+ EARLY_CONTEXT_PARTS+=("\"I notice this directory doesn't have a CLAUDE.md file, so it isn't set up as a Claude Code project yet. Would you like me to:")
185
+ EARLY_CONTEXT_PARTS+=("")
186
+ EARLY_CONTEXT_PARTS+=("1. **Initialise it** — I'll run \`/init\` to scaffold a CLAUDE.md with project context")
187
+ EARLY_CONTEXT_PARTS+=("2. **Skip for now** — Continue without one (you may lose project-specific context)")
188
+ EARLY_CONTEXT_PARTS+=("")
189
+ EARLY_CONTEXT_PARTS+=("What would you prefer?\"")
190
+ EARLY_CONTEXT_PARTS+=("")
191
+ EARLY_CONTEXT_PARTS+=("If the user chooses to initialise, run the /init slash command.")
192
+ EARLY_CONTEXT_PARTS+=("If the user chooses to skip, continue normally.")
193
+
194
+ # Emit everything collected (git warnings + CLAUDE.md missing) and exit
195
+ CONTEXT=$(printf '%s\n' "${EARLY_CONTEXT_PARTS[@]}")
196
+ jq -n --arg ctx "$CONTEXT" '{
197
+ hookSpecificOutput: {
198
+ hookEventName: "SessionStart",
199
+ additionalContext: $ctx
200
+ }
201
+ }'
202
+ exit 0
203
+ fi
204
+
205
+ # ---------------------------------------------------------------------------
206
+ # 2) Staleness evaluation
207
+ # ---------------------------------------------------------------------------
208
+ CLAUDE_MD_MTIME=$(file_mtime "$CLAUDE_MD")
209
+ CLAUDE_MD_AGE_DAYS=$(( (NOW - CLAUDE_MD_MTIME) / 86400 ))
210
+ CLAUDE_MD_CONTENT=$(cat "$CLAUDE_MD")
211
+
212
+ # --- 3a) Age-based check ---------------------------------------------------
213
+ if (( CLAUDE_MD_AGE_DAYS > 14 )); then
214
+ add_staleness "CLAUDE.md was last modified ${CLAUDE_MD_AGE_DAYS} days ago" 2
215
+ elif (( CLAUDE_MD_AGE_DAYS > 7 )); then
216
+ add_staleness "CLAUDE.md was last modified ${CLAUDE_MD_AGE_DAYS} days ago" 1
217
+ fi
218
+
219
+ # --- 3b) Directory structure drift ------------------------------------------
220
+ if command -v tree &>/dev/null; then
221
+ CURRENT_TREE=$(tree -L 2 -d -I 'node_modules|.git|__pycache__|.venv|venv|dist|build|.next|.nuxt|coverage|.claude' --noreport "$PROJECT_DIR" 2>/dev/null) || true
222
+ if [[ -n "$CURRENT_TREE" ]]; then
223
+ # Strip box-drawing characters, pipes, dashes, whitespace to get clean dir names
224
+ TREE_DIRS=$(echo "$CURRENT_TREE" | tail -n +2 \
225
+ | sed 's/[│├└─┬┤┼┐┘┌┏┗┓┛]//g; s/[|`]//g; s/--*//g' \
226
+ | sed 's/^[[:space:]]*//' | { grep -v '^$' || true; } | sort -u)
227
+ MISSING_DIRS=()
228
+ while IFS= read -r dir; do
229
+ [[ -z "$dir" ]] && continue
230
+ if ! grep -qi "$dir" "$CLAUDE_MD" 2>/dev/null; then
231
+ MISSING_DIRS+=("$dir")
232
+ fi
233
+ done <<< "$TREE_DIRS"
234
+ if (( ${#MISSING_DIRS[@]} > 2 )); then
235
+ add_staleness "Directories not mentioned in CLAUDE.md: ${MISSING_DIRS[*]}" 2
236
+ elif (( ${#MISSING_DIRS[@]} > 0 )); then
237
+ add_staleness "Directories not mentioned in CLAUDE.md: ${MISSING_DIRS[*]}" 1
238
+ fi
239
+ fi
240
+ fi
241
+
242
+ # --- 3c) package.json dependency drift --------------------------------------
243
+ if [[ -f "$PROJECT_DIR/package.json" ]]; then
244
+ PKG_MTIME=$(file_mtime "$PROJECT_DIR/package.json")
245
+
246
+ # Check if package.json is newer than CLAUDE.md
247
+ if (( PKG_MTIME > CLAUDE_MD_MTIME )); then
248
+ PKG_DELTA=$(( (PKG_MTIME - CLAUDE_MD_MTIME) / 86400 ))
249
+ add_staleness "package.json was modified ${PKG_DELTA} day(s) after CLAUDE.md" 1
250
+ fi
251
+
252
+ # Count dependencies vs what's documented
253
+ if command -v jq &>/dev/null; then
254
+ DEP_COUNT=$(jq '[(.dependencies // {} | length), (.devDependencies // {} | length)] | add' "$PROJECT_DIR/package.json" 2>/dev/null) || DEP_COUNT="0"
255
+ # Look for numbers near "dependenc" in CLAUDE.md (e.g. "23 dependencies")
256
+ DOCUMENTED_COUNT=$(grep -oiP '\d+\s*(dependencies|deps)' "$CLAUDE_MD" 2>/dev/null | head -1 | grep -oP '\d+') || true
257
+ if [[ -n "$DOCUMENTED_COUNT" && -n "$DEP_COUNT" ]]; then
258
+ DRIFT=$(( DEP_COUNT - DOCUMENTED_COUNT ))
259
+ DRIFT_ABS=${DRIFT#-}
260
+ if (( DRIFT_ABS > 5 )); then
261
+ add_staleness "Dependency count drifted: CLAUDE.md says ~${DOCUMENTED_COUNT}, actual is ${DEP_COUNT} (Δ${DRIFT})" 2
262
+ elif (( DRIFT_ABS > 0 )); then
263
+ add_staleness "Dependency count drifted slightly: CLAUDE.md says ~${DOCUMENTED_COUNT}, actual is ${DEP_COUNT}" 1
264
+ fi
265
+ fi
266
+
267
+ # Check for key dependencies not documented
268
+ KEY_DEPS=$(jq -r '(.dependencies // {}) | keys[]' "$PROJECT_DIR/package.json" 2>/dev/null) || true
269
+ UNDOCUMENTED_KEY_DEPS=()
270
+ while IFS= read -r dep; do
271
+ [[ -z "$dep" ]] && continue
272
+ if ! grep -qi "$dep" "$CLAUDE_MD" 2>/dev/null; then
273
+ UNDOCUMENTED_KEY_DEPS+=("$dep")
274
+ fi
275
+ done <<< "$KEY_DEPS"
276
+ if (( ${#UNDOCUMENTED_KEY_DEPS[@]} > 5 )); then
277
+ SAMPLE="${UNDOCUMENTED_KEY_DEPS[*]:0:5}"
278
+ add_staleness "Multiple production deps not in CLAUDE.md (${#UNDOCUMENTED_KEY_DEPS[@]} total, e.g. ${SAMPLE})" 2
279
+ fi
280
+ fi
281
+ fi
282
+
283
+ # --- 3d) pyproject.toml / requirements.txt drift ----------------------------
284
+ for PYFILE in "$PROJECT_DIR/pyproject.toml" "$PROJECT_DIR/requirements.txt" "$PROJECT_DIR/setup.py"; do
285
+ if [[ -f "$PYFILE" ]]; then
286
+ PY_MTIME=$(file_mtime "$PYFILE")
287
+ if (( PY_MTIME > CLAUDE_MD_MTIME )); then
288
+ add_staleness "$(basename "$PYFILE") was modified after CLAUDE.md" 1
289
+ fi
290
+ fi
291
+ done
292
+
293
+ # --- 3e) Key config files newer than CLAUDE.md ------------------------------
294
+ for CFG in "$PROJECT_DIR/tsconfig.json" \
295
+ "$PROJECT_DIR/.env.example" \
296
+ "$PROJECT_DIR/docker-compose.yml" \
297
+ "$PROJECT_DIR/Dockerfile" \
298
+ "$PROJECT_DIR/Makefile" \
299
+ "$PROJECT_DIR/Cargo.toml" \
300
+ "$PROJECT_DIR/go.mod"; do
301
+ if [[ -f "$CFG" ]]; then
302
+ CFG_MTIME=$(file_mtime "$CFG")
303
+ if (( CFG_MTIME > CLAUDE_MD_MTIME )); then
304
+ add_staleness "$(basename "$CFG") modified after CLAUDE.md" 1
305
+ fi
306
+ fi
307
+ done
308
+
309
+ # --- 3f) Git-based checks ---------------------------------------------------
310
+ if command -v git &>/dev/null && [[ -n "$GIT_ROOT" ]]; then
311
+ # Convert CLAUDE.md mtime to ISO date for git
312
+ CLAUDE_MD_DATE=$(date -d "@$CLAUDE_MD_MTIME" --iso-8601=seconds 2>/dev/null) \
313
+ || CLAUDE_MD_DATE=$(date -r "$CLAUDE_MD_MTIME" +%Y-%m-%dT%H:%M:%S 2>/dev/null) \
314
+ || CLAUDE_MD_DATE=""
315
+
316
+ if [[ -n "$CLAUDE_MD_DATE" ]]; then
317
+ COMMITS_SINCE=$(git -C "$PROJECT_DIR" rev-list --count --since="$CLAUDE_MD_DATE" HEAD 2>/dev/null) || COMMITS_SINCE="0"
318
+ if (( COMMITS_SINCE > 50 )); then
319
+ add_staleness "${COMMITS_SINCE} commits since CLAUDE.md was last updated" 2
320
+ elif (( COMMITS_SINCE > 20 )); then
321
+ add_staleness "${COMMITS_SINCE} commits since CLAUDE.md was last updated" 1
322
+ fi
323
+
324
+ # Check for structural file additions/deletions
325
+ CHANGED_FILES=$(git -C "$PROJECT_DIR" diff --name-only --diff-filter=AD HEAD~20..HEAD 2>/dev/null | head -20) || true
326
+ NEW_TOP_LEVEL=$(echo "$CHANGED_FILES" | { grep -v '/' || true; } | { grep -v '^\.' || true; } | sort -u)
327
+ if [[ -n "$NEW_TOP_LEVEL" ]]; then
328
+ NEW_COUNT=$(echo "$NEW_TOP_LEVEL" | wc -l | tr -d ' ')
329
+ if (( NEW_COUNT > 3 )); then
330
+ add_staleness "Multiple top-level files added/removed since last update (${NEW_COUNT} files)" 1
331
+ fi
332
+ fi
333
+ fi
334
+ fi
335
+
336
+ # --- 3g) Lock file drift (structural indicator) -----------------------------
337
+ for LOCK in "$PROJECT_DIR/package-lock.json" \
338
+ "$PROJECT_DIR/yarn.lock" \
339
+ "$PROJECT_DIR/pnpm-lock.yaml" \
340
+ "$PROJECT_DIR/Cargo.lock" \
341
+ "$PROJECT_DIR/poetry.lock"; do
342
+ if [[ -f "$LOCK" ]]; then
343
+ LOCK_MTIME=$(file_mtime "$LOCK")
344
+ LOCK_DELTA=$(( (LOCK_MTIME - CLAUDE_MD_MTIME) / 86400 ))
345
+ if (( LOCK_DELTA > 7 )); then
346
+ add_staleness "$(basename "$LOCK") is ${LOCK_DELTA} days newer than CLAUDE.md" 1
347
+ break # Only flag once for lock files
348
+ fi
349
+ fi
350
+ done
351
+
352
+ # ---------------------------------------------------------------------------
353
+ # 4) Produce output
354
+ # ---------------------------------------------------------------------------
355
+ OUTPUT_PARTS=()
356
+
357
+ # Include any git warnings collected earlier (monorepo / suspect ancestor)
358
+ for part in "${EARLY_CONTEXT_PARTS[@]+"${EARLY_CONTEXT_PARTS[@]}"}"; do
359
+ OUTPUT_PARTS+=("$part")
360
+ done
361
+
362
+ # Confirm CLAUDE.md found
363
+ OUTPUT_PARTS+=("[SESSION GUARD] ✅ CLAUDE.md found in: $PROJECT_DIR")
364
+
365
+ if (( STALENESS_SCORE >= STALENESS_THRESHOLD )); then
366
+ OUTPUT_PARTS+=("")
367
+ OUTPUT_PARTS+=("[SESSION GUARD] ⚠️ CLAUDE.md appears to be STALE (staleness score: ${STALENESS_SCORE}/${STALENESS_THRESHOLD} threshold)")
368
+ OUTPUT_PARTS+=("")
369
+ OUTPUT_PARTS+=("Staleness signals detected:")
370
+ for sig in "${STALENESS_SIGNALS[@]}"; do
371
+ OUTPUT_PARTS+=(" $sig")
372
+ done
373
+ OUTPUT_PARTS+=("")
374
+ OUTPUT_PARTS+=("Please inform the user with something like:")
375
+ OUTPUT_PARTS+=("")
376
+ OUTPUT_PARTS+=("\"I've detected that your CLAUDE.md may be out of date based on these signals:")
377
+ for sig in "${STALENESS_SIGNALS[@]}"; do
378
+ OUTPUT_PARTS+=("- ${sig#⚠ }")
379
+ done
380
+ OUTPUT_PARTS+=("")
381
+ OUTPUT_PARTS+=("Would you like me to review and update CLAUDE.md to reflect the current state of the project?\"")
382
+ OUTPUT_PARTS+=("")
383
+ OUTPUT_PARTS+=("If the user agrees, read the current CLAUDE.md, analyse the project structure, dependencies, and recent changes, then update CLAUDE.md accordingly. Preserve any user-written notes or conventions that are still accurate.")
384
+ elif (( ${#STALENESS_SIGNALS[@]} > 0 )); then
385
+ OUTPUT_PARTS+=("")
386
+ OUTPUT_PARTS+=("[SESSION GUARD] ℹ️ Minor drift detected (score: ${STALENESS_SCORE}/${STALENESS_THRESHOLD} threshold) — not flagging to user:")
387
+ for sig in "${STALENESS_SIGNALS[@]}"; do
388
+ OUTPUT_PARTS+=(" $sig")
389
+ done
390
+ fi
391
+
392
+ # Join and emit
393
+ CONTEXT=$(printf '%s\n' "${OUTPUT_PARTS[@]}")
394
+
395
+ jq -n --arg ctx "$CONTEXT" '{
396
+ hookSpecificOutput: {
397
+ hookEventName: "SessionStart",
398
+ additionalContext: $ctx
399
+ }
400
+ }'
401
+
402
+ exit 0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@slamb2k/mad-skills",
3
- "version": "2.0.6",
3
+ "version": "2.0.7",
4
4
  "description": "Claude Code skills collection with CI/CD and npx installer",
5
5
  "type": "module",
6
6
  "bin": {
@@ -12,7 +12,11 @@
12
12
  },
13
13
  "files": [
14
14
  "src/",
15
- "skills/"
15
+ "skills/",
16
+ "commands/",
17
+ "agents/",
18
+ "hooks/",
19
+ ".claude-plugin/"
16
20
  ],
17
21
  "scripts": {
18
22
  "validate": "node scripts/validate-skills.js",
@@ -13,18 +13,18 @@ argument-hint: [--no-brace] [--force]
13
13
  # Brace - GOTCHA/BRACE Framework Bootstrap
14
14
 
15
15
  When this skill is invoked, IMMEDIATELY output the banner below before doing anything else.
16
- Pick ONE tagline at random — vary your choice each time:
16
+ Pick ONE tagline at random — vary your choice each time.
17
+ CRITICAL: Reproduce the banner EXACTLY character-for-character. The first line of the art has 4 leading spaces — you MUST preserve them.
17
18
 
18
19
  ```
19
20
  {tagline}
20
21
 
21
- ██╗██████╗ ██████╗ █████╗ ██████╗███████╗
22
+ ██╗██████╗ ██████╗ █████╗ ██████╗███████╗
22
23
  ██╔╝██╔══██╗██╔══██╗██╔══██╗██╔════╝██╔════╝
23
24
  ██╔╝ ██████╔╝██████╔╝███████║██║ █████╗
24
25
  ██╔╝ ██╔══██╗██╔══██╗██╔══██║██║ ██╔══╝
25
26
  ██╔╝ ██████╔╝██║ ██║██║ ██║╚██████╗███████╗
26
27
  ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝╚══════╝
27
-
28
28
  ```
29
29
 
30
30
  Taglines:
@@ -12,18 +12,18 @@ argument-hint: Detailed design/plan to implement
12
12
  # Build - Context-Isolated Feature Development
13
13
 
14
14
  When this skill is invoked, IMMEDIATELY output the banner below before doing anything else.
15
- Pick ONE tagline at random — vary your choice each time:
15
+ Pick ONE tagline at random — vary your choice each time.
16
+ CRITICAL: Reproduce the banner EXACTLY character-for-character. The first line of the art has 4 leading spaces — you MUST preserve them.
16
17
 
17
18
  ```
18
19
  {tagline}
19
20
 
20
- ██╗██████╗ ██╗ ██╗██╗██╗ ██████╗
21
+ ██╗██████╗ ██╗ ██╗██╗██╗ ██████╗
21
22
  ██╔╝██╔══██╗██║ ██║██║██║ ██╔══██╗
22
23
  ██╔╝ ██████╔╝██║ ██║██║██║ ██║ ██║
23
24
  ██╔╝ ██╔══██╗██║ ██║██║██║ ██║ ██║
24
25
  ██╔╝ ██████╔╝╚██████╔╝██║███████╗██████╔╝
25
26
  ╚═╝ ╚═════╝ ╚═════╝ ╚═╝╚══════╝╚═════╝
26
-
27
27
  ```
28
28
 
29
29
  Taglines:
@@ -11,18 +11,18 @@ description: >
11
11
  # Distil - Design Variation Generator
12
12
 
13
13
  When this skill is invoked, IMMEDIATELY output the banner below before doing anything else.
14
- Pick ONE tagline at random — vary your choice each time:
14
+ Pick ONE tagline at random — vary your choice each time.
15
+ CRITICAL: Reproduce the banner EXACTLY character-for-character. The first line of the art has 4 leading spaces — you MUST preserve them.
15
16
 
16
17
  ```
17
18
  {tagline}
18
19
 
19
- ██╗██████╗ ██╗███████╗████████╗██╗██╗
20
+ ██╗██████╗ ██╗███████╗████████╗██╗██╗
20
21
  ██╔╝██╔══██╗██║██╔════╝╚══██╔══╝██║██║
21
22
  ██╔╝ ██║ ██║██║███████╗ ██║ ██║██║
22
23
  ██╔╝ ██║ ██║██║╚════██║ ██║ ██║██║
23
24
  ██╔╝ ██████╔╝██║███████║ ██║ ██║███████╗
24
25
  ╚═╝ ╚═════╝ ╚═╝╚══════╝ ╚═╝ ╚═╝╚══════╝
25
-
26
26
  ```
27
27
 
28
28
  Taglines:
@@ -1,5 +1,5 @@
1
1
  {
2
- "generated": "2026-02-28T05:58:04.217Z",
2
+ "generated": "2026-03-03T01:58:15.572Z",
3
3
  "count": 7,
4
4
  "skills": [
5
5
  {
@@ -12,18 +12,18 @@ description: >
12
12
  # Prime - Project Context Loader
13
13
 
14
14
  When this skill is invoked, IMMEDIATELY output the banner below before doing anything else.
15
- Pick ONE tagline at random — vary your choice each time:
15
+ Pick ONE tagline at random — vary your choice each time.
16
+ CRITICAL: Reproduce the banner EXACTLY character-for-character. The first line of the art has 4 leading spaces — you MUST preserve them.
16
17
 
17
18
  ```
18
19
  {tagline}
19
20
 
20
- ██╗██████╗ ██████╗ ██╗███╗ ███╗███████╗
21
+ ██╗██████╗ ██████╗ ██╗███╗ ███╗███████╗
21
22
  ██╔╝██╔══██╗██╔══██╗██║████╗ ████║██╔════╝
22
23
  ██╔╝ ██████╔╝██████╔╝██║██╔████╔██║█████╗
23
24
  ██╔╝ ██╔═══╝ ██╔══██╗██║██║╚██╔╝██║██╔══╝
24
25
  ██╔╝ ██║ ██║ ██║██║██║ ╚═╝ ██║███████╗
25
26
  ╚═╝ ╚═╝ ╚═╝ ╚═╝╚═╝╚═╝ ╚═╝╚══════╝
26
-
27
27
  ```
28
28
 
29
29
  Taglines:
@@ -11,18 +11,18 @@ description: >
11
11
  # Rig - Repository Bootstrap
12
12
 
13
13
  When this skill is invoked, IMMEDIATELY output the banner below before doing anything else.
14
- Pick ONE tagline at random — vary your choice each time:
14
+ Pick ONE tagline at random — vary your choice each time.
15
+ CRITICAL: Reproduce the banner EXACTLY character-for-character. The first line of the art has 4 leading spaces — you MUST preserve them.
15
16
 
16
17
  ```
17
18
  {tagline}
18
19
 
19
- ██╗██████╗ ██╗ ██████╗
20
+ ██╗██████╗ ██╗ ██████╗
20
21
  ██╔╝██╔══██╗██║██╔════╝
21
22
  ██╔╝ ██████╔╝██║██║ ███╗
22
23
  ██╔╝ ██╔══██╗██║██║ ██║
23
24
  ██╔╝ ██║ ██║██║╚██████╔╝
24
25
  ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═════╝
25
-
26
26
  ```
27
27
 
28
28
  Taglines:
@@ -11,18 +11,18 @@ description: >
11
11
  # Ship - Full PR Lifecycle
12
12
 
13
13
  When this skill is invoked, IMMEDIATELY output the banner below before doing anything else.
14
- Pick ONE tagline at random — vary your choice each time:
14
+ Pick ONE tagline at random — vary your choice each time.
15
+ CRITICAL: Reproduce the banner EXACTLY character-for-character. The first line of the art has 4 leading spaces — you MUST preserve them.
15
16
 
16
17
  ```
17
18
  {tagline}
18
19
 
19
- ██╗███████╗██╗ ██╗██╗██████╗
20
+ ██╗███████╗██╗ ██╗██╗██████╗
20
21
  ██╔╝██╔════╝██║ ██║██║██╔══██╗
21
22
  ██╔╝ ███████╗███████║██║██████╔╝
22
23
  ██╔╝ ╚════██║██╔══██║██║██╔═══╝
23
24
  ██╔╝ ███████║██║ ██║██║██║
24
25
  ╚═╝ ╚══════╝╚═╝ ╚═╝╚═╝╚═╝
25
-
26
26
  ```
27
27
 
28
28
  Taglines:
@@ -11,18 +11,18 @@ description: >
11
11
  # Sync - Repository Synchronization
12
12
 
13
13
  When this skill is invoked, IMMEDIATELY output the banner below before doing anything else.
14
- Pick ONE tagline at random — vary your choice each time:
14
+ Pick ONE tagline at random — vary your choice each time.
15
+ CRITICAL: Reproduce the banner EXACTLY character-for-character. The first line of the art has 4 leading spaces — you MUST preserve them.
15
16
 
16
17
  ```
17
18
  {tagline}
18
19
 
19
- ██╗███████╗██╗ ██╗███╗ ██╗ ██████╗
20
+ ██╗███████╗██╗ ██╗███╗ ██╗ ██████╗
20
21
  ██╔╝██╔════╝╚██╗ ██╔╝████╗ ██║██╔════╝
21
22
  ██╔╝ ███████╗ ╚████╔╝ ██╔██╗ ██║██║
22
23
  ██╔╝ ╚════██║ ╚██╔╝ ██║╚██╗██║██║
23
24
  ██╔╝ ███████║ ██║ ██║ ╚████║╚██████╗
24
25
  ╚═╝ ╚══════╝ ╚═╝ ╚═╝ ╚═══╝ ╚═════╝
25
-
26
26
  ```
27
27
 
28
28
  Taglines:
package/src/cli.js CHANGED
@@ -11,7 +11,7 @@
11
11
  * npx @your-scope/claude-skills --upgrade # Upgrade existing skills
12
12
  */
13
13
 
14
- import { readdir, readFile, mkdir, access, stat } from "node:fs/promises";
14
+ import { readdir, readFile, mkdir, access, stat, chmod } from "node:fs/promises";
15
15
  import { existsSync } from "node:fs";
16
16
  import { execSync } from "node:child_process";
17
17
  import { resolve, join, dirname } from "node:path";
@@ -20,6 +20,7 @@ import { parseArgs } from "node:util";
20
20
 
21
21
  const __dirname = dirname(fileURLToPath(import.meta.url));
22
22
  const SKILLS_SRC = resolve(__dirname, "..", "skills");
23
+ const SUPPLEMENTARY_DIRS = ["commands", "agents", "hooks"];
23
24
 
24
25
  const DEFAULT_TARGETS = [
25
26
  ".claude/skills", // Project-level (preferred)
@@ -42,21 +43,23 @@ if (args.help) {
42
43
  console.log(`
43
44
  Claude Skills Installer
44
45
 
46
+ Installs skills, slash commands, agents, and hooks to your .claude directory.
47
+
45
48
  Usage:
46
- npx @your-scope/claude-skills [options]
49
+ npx @slamb2k/mad-skills [options]
47
50
 
48
51
  Options:
49
52
  --list List available skills with descriptions
50
53
  --skill <names> Comma-separated skill names to install (default: all)
51
54
  --target <path> Installation directory (default: .claude/skills)
52
- --upgrade Overwrite existing skills
55
+ --upgrade Overwrite existing skills and supplementary files
53
56
  --force, -f Skip confirmation prompts
54
57
  --help, -h Show this help
55
58
 
56
59
  Examples:
57
- npx @your-scope/claude-skills --list
58
- npx @your-scope/claude-skills --skill git-workflow,mcp-builder
59
- npx @your-scope/claude-skills --target ./my-project/.claude/skills --upgrade
60
+ npx @slamb2k/mad-skills --list
61
+ npx @slamb2k/mad-skills --skill build,ship
62
+ npx @slamb2k/mad-skills --target ./my-project/.claude/skills --upgrade
60
63
  `);
61
64
  process.exit(0);
62
65
  }
@@ -326,6 +329,55 @@ async function cpFiltered(src, dest) {
326
329
  }
327
330
  }
328
331
 
332
+ /**
333
+ * Install supplementary directories (commands, agents, hooks) alongside skills.
334
+ * Target dirs are siblings of the skills target dir (e.g. ~/.claude/commands).
335
+ */
336
+ async function installSupplementary(targetDir) {
337
+ const baseDir = dirname(targetDir);
338
+ const results = { installed: 0, upgraded: 0, skipped: 0 };
339
+
340
+ for (const dir of SUPPLEMENTARY_DIRS) {
341
+ const src = resolve(__dirname, "..", dir);
342
+ const dest = join(baseDir, dir);
343
+
344
+ if (!existsSync(src)) continue;
345
+
346
+ const entries = await readdir(src, { withFileTypes: true });
347
+ await mkdir(dest, { recursive: true });
348
+
349
+ for (const entry of entries) {
350
+ if (!entry.isFile()) continue;
351
+ if (entry.name.startsWith(".")) continue;
352
+
353
+ const srcPath = join(src, entry.name);
354
+ const destPath = join(dest, entry.name);
355
+ const destExists = await exists(destPath);
356
+
357
+ if (destExists && !args.upgrade) {
358
+ results.skipped++;
359
+ continue;
360
+ }
361
+
362
+ const { copyFile } = await import("node:fs/promises");
363
+ await copyFile(srcPath, destPath);
364
+
365
+ // Make shell scripts executable
366
+ if (entry.name.endsWith(".sh")) {
367
+ await chmod(destPath, 0o755);
368
+ }
369
+
370
+ if (destExists) {
371
+ results.upgraded++;
372
+ } else {
373
+ results.installed++;
374
+ }
375
+ }
376
+ }
377
+
378
+ return results;
379
+ }
380
+
329
381
  async function main() {
330
382
  const skills = await discoverSkills();
331
383
 
@@ -410,6 +462,17 @@ async function main() {
410
462
  }
411
463
  console.log(` ${parts.join(", ")}`);
412
464
  }
465
+
466
+ // Pass 3: Install supplementary files (commands, agents, hooks)
467
+ const suppResults = await installSupplementary(targetDir);
468
+ const suppTotal = suppResults.installed + suppResults.upgraded + suppResults.skipped;
469
+
470
+ if (suppTotal > 0) {
471
+ console.log(
472
+ `\nSupplementary files: ${suppResults.installed} installed, ${suppResults.upgraded} upgraded, ${suppResults.skipped} skipped`
473
+ );
474
+ }
475
+
413
476
  console.log();
414
477
  }
415
478