-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathsetup
More file actions
executable file
·187 lines (170 loc) · 7.04 KB
/
Copy pathsetup
File metadata and controls
executable file
·187 lines (170 loc) · 7.04 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
#!/usr/bin/env bash
# pmstack installer — copies CLAUDE.md, skills, templates, and slash commands into a target project.
# Usage:
# ./setup # install into current dir (must be a project root)
# ./setup /path/to/project # install into a specific project dir
# ./setup --global # install commands + skills to ~/.claude (available in every session)
# ./setup --dry-run # show what would be copied
set -euo pipefail
SRC="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
TARGET="$(pwd)"
DRY_RUN=0
GLOBAL=0
for arg in "$@"; do
case "$arg" in
--global) GLOBAL=1 ;;
--dry-run) DRY_RUN=1 ;;
-h|--help)
# Print the leading comment block (lines 2..first non-# line) so help never drifts from the comment.
awk 'NR==1{next} /^#/{sub(/^# ?/,""); print; next} {exit}' "$0"
exit 0
;;
*)
if [[ -d "$arg" ]]; then
TARGET="$(cd "$arg" && pwd)"
else
echo "Unknown arg or missing dir: $arg" >&2
exit 1
fi
;;
esac
done
if [[ "$GLOBAL" == "1" ]]; then
TARGET="$HOME/.claude"
# Global install: commands live at ~/.claude/commands and Anthropic Skills at ~/.claude/skills (no nested .claude).
COMMANDS_DIR="$TARGET/commands"
AGENT_SKILLS_DIR="$TARGET/skills"
echo "→ Installing pmstack globally to $TARGET"
else
# Project install: commands live at <project>/.claude/commands.
# Note: pmstack uses skills/ in project mode for the per-command skill source files.
# Anthropic Agent Skills install to .claude/skills/ in project mode (Claude Code reads both).
COMMANDS_DIR="$TARGET/.claude/commands"
AGENT_SKILLS_DIR="$TARGET/.claude/skills"
echo "→ Installing pmstack into $TARGET"
fi
run() {
echo " $*"
if [[ "$DRY_RUN" == "0" ]]; then "$@"; fi
}
run mkdir -p "$COMMANDS_DIR" "$AGENT_SKILLS_DIR" "$TARGET/skills" "$TARGET/templates" "$TARGET/outputs" "$TARGET/bin" "$TARGET/docs" "$TARGET/evals/golden" "$TARGET/examples/golden"
# Slash commands — required for /eval, /prd, etc. to work. The list is the
# directory itself, so a new command can never silently miss the installer.
for f in "$SRC"/.claude/commands/*.md; do
run cp "$f" "$COMMANDS_DIR/$(basename "$f")"
done
# Skills + templates — referenced by the commands. All flat files, not just
# *.md: commands also reference skills/_graph.yaml.
for f in "$SRC"/skills/*; do
[[ -f "$f" ]] && run cp "$f" "$TARGET/skills/$(basename "$f")"
done
for f in "$SRC"/templates/*; do
run cp "$f" "$TARGET/templates/$(basename "$f")"
done
# Anthropic Agent Skills — cross-platform (web/mobile/desktop/API/CLI).
for d in "$SRC"/claude-skills/*/; do
[[ -d "$d" ]] || continue
name="$(basename "$d")"
run mkdir -p "$AGENT_SKILLS_DIR/$name"
for f in "$d"*; do
[[ -f "$f" ]] && run cp "$f" "$AGENT_SKILLS_DIR/$name/$(basename "$f")"
done
done
# bin/ — runner script for /run-eval. Required for actual eval execution.
for f in "$SRC"/bin/*; do
[[ -f "$f" ]] && run cp "$f" "$TARGET/bin/$(basename "$f")"
[[ -f "$f" ]] && run chmod +x "$TARGET/bin/$(basename "$f")"
done
# evals/ — runner.py + regression-check.py + golden baseline + suite definition.
run mkdir -p "$TARGET/evals"
for f in "$SRC"/evals/runner.py "$SRC"/evals/regression-check.py "$SRC"/evals/pmstack-self.yaml "$SRC"/evals/README.md; do
[[ -f "$f" ]] && run cp "$f" "$TARGET/evals/$(basename "$f")"
done
[[ -f "$SRC/evals/runner.py" ]] && run chmod +x "$TARGET/evals/runner.py"
[[ -f "$SRC/evals/regression-check.py" ]] && run chmod +x "$TARGET/evals/regression-check.py"
for f in "$SRC"/evals/golden/*; do
[[ -f "$f" ]] && run cp "$f" "$TARGET/evals/golden/$(basename "$f")"
done
# examples/golden/ — anchor outputs index.
for f in "$SRC"/examples/golden/*; do
[[ -f "$f" ]] && run cp "$f" "$TARGET/examples/golden/$(basename "$f")"
done
# examples/walkthrough-code-review/ — /onboarding walks this artifact set
# (commands reference @examples/walkthrough-code-review/), so it ships too.
while IFS= read -r -d '' f; do
rel="${f#"$SRC"/}"
run mkdir -p "$TARGET/$(dirname "$rel")"
run cp "$f" "$TARGET/$rel"
done < <(find "$SRC/examples/walkthrough-code-review" -type f -print0)
# docs/ — learn-by-doing guides for PMs new to evals etc.
for f in "$SRC"/docs/*; do
[[ -f "$f" ]] && run cp "$f" "$TARGET/docs/$(basename "$f")"
done
# Global install: commands and skills reference @skills/..., @templates/...,
# `bin/run-eval.py` etc. relative to a project root. Claude Code resolves
# @-references against the *current working directory*, so from any other
# folder they silently fail to load. Rewrite them to absolute ~/.claude paths
# at install time. (Project installs keep relative refs — they resolve from
# the project root, which is where you run claude.)
if [[ "$GLOBAL" == "1" ]]; then
rewrite_refs() {
sed -e "s|@skills/|@$TARGET/skills/|g" \
-e "s|@templates/|@$TARGET/templates/|g" \
-e "s|@evals/|@$TARGET/evals/|g" \
-e "s|@examples/|@$TARGET/examples/|g" \
-e "s|\`bin/run-eval.py|\`$TARGET/bin/run-eval.py|g" \
-e "s|python3 evals/runner.py|python3 $TARGET/evals/runner.py|g" \
-e "s|python3 evals/regression-check.py|python3 $TARGET/evals/regression-check.py|g" \
"$1" > "$1.pmstack-rewrite" && mv "$1.pmstack-rewrite" "$1"
}
echo " rewriting relative @-references to $TARGET/... in installed commands + skills"
if [[ "$DRY_RUN" == "0" ]]; then
for f in "$COMMANDS_DIR"/*.md "$TARGET"/skills/*.md; do
[[ -f "$f" ]] && rewrite_refs "$f"
done
fi
fi
# CLAUDE.md — only at project scope, never overwrite.
if [[ "$GLOBAL" == "0" ]]; then
if [[ -f "$TARGET/CLAUDE.md" ]]; then
echo " ⚠ CLAUDE.md already exists at $TARGET — leaving it alone."
echo " Merge in pmstack's CLAUDE.md by hand if you want the PM persona."
else
run cp "$SRC/CLAUDE.md" "$TARGET/CLAUDE.md"
fi
fi
# .gitignore — keep user-generated PM artifacts out of git history by default.
# Project scope only. Append idempotently — never clobber existing rules.
if [[ "$GLOBAL" == "0" ]]; then
GI="$TARGET/.gitignore"
MARKER="# >>> pmstack >>>"
if [[ -f "$GI" ]] && grep -q "$MARKER" "$GI" 2>/dev/null; then
echo " ⓘ .gitignore already has pmstack block — leaving alone."
else
if [[ "$DRY_RUN" == "0" ]]; then
{
[[ -f "$GI" ]] && echo ""
echo "$MARKER"
echo "# Generated by pmstack ./setup. Edit if you want different rules."
echo "outputs/"
echo "evals/results/"
echo "# <<< pmstack <<<"
} >> "$GI"
fi
echo " appended pmstack block to $GI"
fi
fi
echo ""
echo "✓ pmstack installed."
echo ""
echo "Try it:"
echo " claude"
echo " > /prd \"Customers say onboarding takes 3 days\""
echo " > /sprint \"Customers want pricing transparency\""
echo " > /compare Cursor Windsurf"
echo " > /eval Claude Ultraplan"
echo " > /run-eval outputs/eval-<feature>-<date>.yaml"
echo " > /eval-self # score pmstack itself"
echo ""
echo "Anthropic Agent Skills installed (work in Claude.ai web/mobile + API too):"
echo " Upload claude-skills/<name>/ folders to a Claude.ai Project."