@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.
- package/.claude-plugin/marketplace.json +24 -0
- package/.claude-plugin/plugin.json +16 -0
- package/agents/ship-analyzer.md +91 -0
- package/commands/brace.md +9 -0
- package/commands/build.md +9 -0
- package/commands/distil.md +9 -0
- package/commands/prime.md +9 -0
- package/commands/rig.md +9 -0
- package/commands/ship.md +9 -0
- package/commands/sync.md +9 -0
- package/hooks/session-guard.sh +402 -0
- package/package.json +6 -2
- package/skills/brace/SKILL.md +3 -3
- package/skills/build/SKILL.md +3 -3
- package/skills/distil/SKILL.md +3 -3
- package/skills/manifest.json +1 -1
- package/skills/prime/SKILL.md +3 -3
- package/skills/rig/SKILL.md +3 -3
- package/skills/ship/SKILL.md +3 -3
- package/skills/sync/SKILL.md +3 -3
- package/src/cli.js +69 -6
|
@@ -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
|
package/commands/rig.md
ADDED
|
@@ -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
|
package/commands/ship.md
ADDED
|
@@ -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
|
package/commands/sync.md
ADDED
|
@@ -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.
|
|
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",
|
package/skills/brace/SKILL.md
CHANGED
|
@@ -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:
|
package/skills/build/SKILL.md
CHANGED
|
@@ -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:
|
package/skills/distil/SKILL.md
CHANGED
|
@@ -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:
|
package/skills/manifest.json
CHANGED
package/skills/prime/SKILL.md
CHANGED
|
@@ -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:
|
package/skills/rig/SKILL.md
CHANGED
|
@@ -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:
|
package/skills/ship/SKILL.md
CHANGED
|
@@ -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:
|
package/skills/sync/SKILL.md
CHANGED
|
@@ -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 @
|
|
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 @
|
|
58
|
-
npx @
|
|
59
|
-
npx @
|
|
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
|
|