wayfind 0.0.1 → 2.0.0
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/BOOTSTRAP_PROMPT.md +120 -0
- package/bin/connectors/github.js +617 -0
- package/bin/connectors/index.js +13 -0
- package/bin/connectors/intercom.js +595 -0
- package/bin/connectors/llm.js +469 -0
- package/bin/connectors/notion.js +747 -0
- package/bin/connectors/transport.js +325 -0
- package/bin/content-store.js +2006 -0
- package/bin/digest.js +813 -0
- package/bin/rebuild-status.js +297 -0
- package/bin/slack-bot.js +1535 -0
- package/bin/slack.js +342 -0
- package/bin/storage/index.js +171 -0
- package/bin/storage/json-backend.js +348 -0
- package/bin/storage/sqlite-backend.js +415 -0
- package/bin/team-context.js +4209 -0
- package/bin/telemetry.js +159 -0
- package/doctor.sh +291 -0
- package/install.sh +144 -0
- package/journal-summary.sh +577 -0
- package/package.json +48 -6
- package/setup.sh +641 -0
- package/specializations/claude-code/CLAUDE.md-global-fragment.md +53 -0
- package/specializations/claude-code/CLAUDE.md-repo-fragment.md +16 -0
- package/specializations/claude-code/README.md +99 -0
- package/specializations/claude-code/commands/doctor.md +31 -0
- package/specializations/claude-code/commands/init-memory.md +154 -0
- package/specializations/claude-code/commands/init-team.md +415 -0
- package/specializations/claude-code/commands/journal.md +66 -0
- package/specializations/claude-code/commands/review-prs.md +119 -0
- package/specializations/claude-code/hooks/check-global-state.sh +20 -0
- package/specializations/claude-code/hooks/session-end.sh +36 -0
- package/specializations/claude-code/settings.json +15 -0
- package/specializations/cursor/README.md +120 -0
- package/specializations/cursor/global-rule.mdc +53 -0
- package/specializations/cursor/repo-rule.mdc +25 -0
- package/specializations/generic/README.md +47 -0
- package/templates/autopilot/design.md +22 -0
- package/templates/autopilot/engineering.md +22 -0
- package/templates/autopilot/product.md +22 -0
- package/templates/autopilot/strategy.md +22 -0
- package/templates/autopilot/unified.md +24 -0
- package/templates/deploy/.env.example +110 -0
- package/templates/deploy/docker-compose.yml +63 -0
- package/templates/deploy/slack-app-manifest.json +45 -0
- package/templates/github-actions/meridian-digest.yml +85 -0
- package/templates/global.md +79 -0
- package/templates/memory-file.md +18 -0
- package/templates/personal-state.md +14 -0
- package/templates/personas.json +28 -0
- package/templates/product-state.md +41 -0
- package/templates/prompts-readme.md +19 -0
- package/templates/repo-state.md +18 -0
- package/templates/session-protocol-fragment.md +46 -0
- package/templates/slack-app-manifest.json +27 -0
- package/templates/statusline.sh +22 -0
- package/templates/strategy-state.md +39 -0
- package/templates/team-state.md +55 -0
- package/uninstall.sh +105 -0
- package/README.md +0 -4
package/setup.sh
ADDED
|
@@ -0,0 +1,641 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Wayfind — Setup Script
|
|
3
|
+
# Installs the memory system for your AI coding assistant.
|
|
4
|
+
# Safe to re-run (idempotent). Backs up existing files before modifying.
|
|
5
|
+
#
|
|
6
|
+
# Usage:
|
|
7
|
+
# bash setup.sh # Interactive — prompts for tool choice
|
|
8
|
+
# bash setup.sh --tool claude-code # Non-interactive
|
|
9
|
+
# bash setup.sh --tool cursor
|
|
10
|
+
# bash setup.sh --tool generic
|
|
11
|
+
# bash setup.sh --dry-run # Preview changes without modifying files
|
|
12
|
+
# bash setup.sh --version # Print installed version and exit
|
|
13
|
+
# bash setup.sh --help # Show this help
|
|
14
|
+
|
|
15
|
+
set -euo pipefail
|
|
16
|
+
|
|
17
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
18
|
+
TOOL=""
|
|
19
|
+
DRY_RUN=false
|
|
20
|
+
REPO_DIR=""
|
|
21
|
+
UPDATE=false
|
|
22
|
+
|
|
23
|
+
# ── Argument parsing ──────────────────────────────────────────────────────────
|
|
24
|
+
|
|
25
|
+
usage() {
|
|
26
|
+
cat <<USAGE
|
|
27
|
+
Usage: bash setup.sh [OPTIONS]
|
|
28
|
+
|
|
29
|
+
Options:
|
|
30
|
+
--tool=TOOL Specify tool without prompt (claude-code, cursor, generic)
|
|
31
|
+
--tool TOOL Same as above (space-separated form)
|
|
32
|
+
--repo=PATH Install per-repo rule into PATH (Cursor only)
|
|
33
|
+
--repo PATH Same as above (space-separated form)
|
|
34
|
+
--update Update mode: overwrite hook scripts and commands (memory files untouched)
|
|
35
|
+
--dry-run Preview what would be installed without modifying files
|
|
36
|
+
--version Print installed version and exit
|
|
37
|
+
--help Show this help message
|
|
38
|
+
|
|
39
|
+
Examples:
|
|
40
|
+
bash setup.sh
|
|
41
|
+
bash setup.sh --tool claude-code
|
|
42
|
+
bash setup.sh --tool=cursor --repo .
|
|
43
|
+
bash setup.sh --tool claude-code --update --dry-run
|
|
44
|
+
USAGE
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
while [[ $# -gt 0 ]]; do
|
|
48
|
+
case "$1" in
|
|
49
|
+
--tool=*) TOOL="${1#--tool=}"; shift ;;
|
|
50
|
+
--tool)
|
|
51
|
+
if [ -z "${2:-}" ]; then
|
|
52
|
+
echo "Error: --tool requires a value (claude-code, cursor, or generic)"
|
|
53
|
+
exit 1
|
|
54
|
+
fi
|
|
55
|
+
TOOL="$2"; shift 2 ;;
|
|
56
|
+
--repo=*) REPO_DIR="$(cd "${1#--repo=}" 2>/dev/null && pwd || echo "")"; shift ;;
|
|
57
|
+
--repo)
|
|
58
|
+
if [ -z "${2:-}" ]; then
|
|
59
|
+
echo "Error: --repo requires a path"
|
|
60
|
+
exit 1
|
|
61
|
+
fi
|
|
62
|
+
REPO_DIR="$(cd "$2" 2>/dev/null && pwd || echo "")"; shift 2 ;;
|
|
63
|
+
--update) UPDATE=true; shift ;;
|
|
64
|
+
--dry-run) DRY_RUN=true; shift ;;
|
|
65
|
+
--version)
|
|
66
|
+
VERSION_FILE="$HOME/.claude/team-context/.wayfind-version"
|
|
67
|
+
if [ -f "$VERSION_FILE" ]; then
|
|
68
|
+
echo "Wayfind v$(cat "$VERSION_FILE")"
|
|
69
|
+
else
|
|
70
|
+
echo "Wayfind version unknown (no .wayfind-version file found)"
|
|
71
|
+
fi
|
|
72
|
+
exit 0
|
|
73
|
+
;;
|
|
74
|
+
--help) usage; exit 0 ;;
|
|
75
|
+
*) shift ;;
|
|
76
|
+
esac
|
|
77
|
+
done
|
|
78
|
+
|
|
79
|
+
# ── Helpers ───────────────────────────────────────────────────────────────────
|
|
80
|
+
|
|
81
|
+
GREEN='\033[0;32m'
|
|
82
|
+
YELLOW='\033[1;33m'
|
|
83
|
+
RED='\033[0;31m'
|
|
84
|
+
RESET='\033[0m'
|
|
85
|
+
|
|
86
|
+
log() { echo -e "${GREEN}✓${RESET} $1"; }
|
|
87
|
+
warn() { echo -e "${YELLOW}⚠${RESET} $1"; }
|
|
88
|
+
info() { echo " $1"; }
|
|
89
|
+
header() { echo ""; echo -e "${GREEN}── $1 ──${RESET}"; }
|
|
90
|
+
|
|
91
|
+
backup_if_exists() {
|
|
92
|
+
local file="$1"
|
|
93
|
+
if [ -f "$file" ]; then
|
|
94
|
+
local backup="${file}.bak.$(date +%Y%m%d%H%M%S)"
|
|
95
|
+
cp "$file" "$backup"
|
|
96
|
+
warn "Backed up existing $file → $backup"
|
|
97
|
+
fi
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
run() {
|
|
101
|
+
if [ "$DRY_RUN" = true ]; then
|
|
102
|
+
info "[dry-run] $*"
|
|
103
|
+
else
|
|
104
|
+
"$@"
|
|
105
|
+
fi
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
append_if_missing() {
|
|
109
|
+
local needle="$1"
|
|
110
|
+
local content="$2"
|
|
111
|
+
local file="$3"
|
|
112
|
+
if ! grep -qF "$needle" "$file" 2>/dev/null; then
|
|
113
|
+
echo "$content" >> "$file"
|
|
114
|
+
log "Appended to $file"
|
|
115
|
+
else
|
|
116
|
+
info "Already present in $file — skipped"
|
|
117
|
+
fi
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
# ── Tool selection ─────────────────────────────────────────────────────────────
|
|
121
|
+
|
|
122
|
+
if [ -z "$TOOL" ]; then
|
|
123
|
+
echo ""
|
|
124
|
+
echo "Wayfind — Setup"
|
|
125
|
+
echo "─────────────────────"
|
|
126
|
+
echo "Which AI tool are you setting up for?"
|
|
127
|
+
echo " 1) Claude Code"
|
|
128
|
+
echo " 2) Cursor"
|
|
129
|
+
echo " 3) Generic (system prompt / manual)"
|
|
130
|
+
echo ""
|
|
131
|
+
read -rp "Enter choice [1-3]: " choice
|
|
132
|
+
case "$choice" in
|
|
133
|
+
1) TOOL="claude-code" ;;
|
|
134
|
+
2) TOOL="cursor" ;;
|
|
135
|
+
3) TOOL="generic" ;;
|
|
136
|
+
*) echo "Invalid choice. Exiting."; exit 1 ;;
|
|
137
|
+
esac
|
|
138
|
+
fi
|
|
139
|
+
|
|
140
|
+
SPEC_DIR="$SCRIPT_DIR/specializations/$TOOL"
|
|
141
|
+
if [ ! -d "$SPEC_DIR" ]; then
|
|
142
|
+
echo -e "${RED}Error:${RESET} No specialization found for '$TOOL' at $SPEC_DIR"
|
|
143
|
+
echo "Available: $(ls "$SCRIPT_DIR/specializations/")"
|
|
144
|
+
exit 1
|
|
145
|
+
fi
|
|
146
|
+
|
|
147
|
+
echo ""
|
|
148
|
+
echo "Installing Wayfind for: $TOOL"
|
|
149
|
+
[ "$DRY_RUN" = true ] && warn "Dry-run mode — no files will be modified"
|
|
150
|
+
[ "$UPDATE" = true ] && warn "Update mode: overwriting hook scripts and commands (memory files untouched)"
|
|
151
|
+
|
|
152
|
+
# Show upgrade messaging if version info is available (passed from install.sh)
|
|
153
|
+
WAYFIND_OLD_VERSION="${WAYFIND_OLD_VERSION:-}"
|
|
154
|
+
WAYFIND_NEW_VERSION="${WAYFIND_NEW_VERSION:-}"
|
|
155
|
+
if [ "$UPDATE" = true ] && [ -n "$WAYFIND_OLD_VERSION" ] && [ -n "$WAYFIND_NEW_VERSION" ]; then
|
|
156
|
+
if [ "$WAYFIND_OLD_VERSION" != "$WAYFIND_NEW_VERSION" ]; then
|
|
157
|
+
info "Upgrading from v${WAYFIND_OLD_VERSION} to v${WAYFIND_NEW_VERSION}"
|
|
158
|
+
fi
|
|
159
|
+
fi
|
|
160
|
+
|
|
161
|
+
# ── Tool-specific config ───────────────────────────────────────────────────────
|
|
162
|
+
|
|
163
|
+
case "$TOOL" in
|
|
164
|
+
claude-code)
|
|
165
|
+
MEMORY_DIR="$HOME/.claude"
|
|
166
|
+
MEMORY_SUBDIR="$HOME/.claude/memory"
|
|
167
|
+
GLOBAL_STATE="$HOME/.claude/global-state.md"
|
|
168
|
+
ADMIN_STATE="$HOME/.claude/state.md"
|
|
169
|
+
GLOBAL_CLAUDE_MD="$HOME/.claude/CLAUDE.md"
|
|
170
|
+
;;
|
|
171
|
+
cursor)
|
|
172
|
+
MEMORY_DIR="$HOME/.ai-memory"
|
|
173
|
+
MEMORY_SUBDIR="$HOME/.ai-memory/memory"
|
|
174
|
+
GLOBAL_STATE="$HOME/.ai-memory/global.md"
|
|
175
|
+
ADMIN_STATE="$HOME/.ai-memory/state.md"
|
|
176
|
+
GLOBAL_CLAUDE_MD="" # No global instructions file for Cursor
|
|
177
|
+
;;
|
|
178
|
+
generic)
|
|
179
|
+
MEMORY_DIR="$HOME/.ai-memory"
|
|
180
|
+
MEMORY_SUBDIR="$HOME/.ai-memory/memory"
|
|
181
|
+
GLOBAL_STATE="$HOME/.ai-memory/global.md"
|
|
182
|
+
ADMIN_STATE="$HOME/.ai-memory/state.md"
|
|
183
|
+
GLOBAL_CLAUDE_MD=""
|
|
184
|
+
;;
|
|
185
|
+
esac
|
|
186
|
+
|
|
187
|
+
# ── Step 1: Directories ────────────────────────────────────────────────────────
|
|
188
|
+
|
|
189
|
+
header "Creating directories"
|
|
190
|
+
|
|
191
|
+
run mkdir -p "$MEMORY_SUBDIR/journal"
|
|
192
|
+
log "Memory directories: $MEMORY_SUBDIR/journal"
|
|
193
|
+
|
|
194
|
+
if [ "$TOOL" = "claude-code" ]; then
|
|
195
|
+
run mkdir -p "$HOME/.claude/hooks"
|
|
196
|
+
run mkdir -p "$HOME/.claude/commands"
|
|
197
|
+
log "Claude Code hooks and commands directories"
|
|
198
|
+
fi
|
|
199
|
+
|
|
200
|
+
# ── Step 2: Global state file ─────────────────────────────────────────────────
|
|
201
|
+
|
|
202
|
+
header "Global state file"
|
|
203
|
+
|
|
204
|
+
if [ ! -f "$GLOBAL_STATE" ]; then
|
|
205
|
+
if [ "$TOOL" = "claude-code" ]; then
|
|
206
|
+
if [ "$DRY_RUN" = false ]; then
|
|
207
|
+
sed \
|
|
208
|
+
-e 's|~/.ai-memory/|~/.claude/|g' \
|
|
209
|
+
-e 's|\.ai-memory/state\.md|.claude/state.md|g' \
|
|
210
|
+
"$SCRIPT_DIR/templates/global.md" > "$GLOBAL_STATE"
|
|
211
|
+
else
|
|
212
|
+
info "[dry-run] Would sed (path substitution) templates/global.md > $GLOBAL_STATE"
|
|
213
|
+
fi
|
|
214
|
+
else
|
|
215
|
+
run cp "$SCRIPT_DIR/templates/global.md" "$GLOBAL_STATE"
|
|
216
|
+
fi
|
|
217
|
+
log "Created $GLOBAL_STATE"
|
|
218
|
+
warn "Edit $GLOBAL_STATE to add your name, preferences, and projects"
|
|
219
|
+
else
|
|
220
|
+
info "Already exists: $GLOBAL_STATE — skipped"
|
|
221
|
+
fi
|
|
222
|
+
|
|
223
|
+
# ── Step 3: Admin state file (team/personal split) ───────────────────────────
|
|
224
|
+
# For team repos, use templates/team-state.md (committed, shared context).
|
|
225
|
+
# For personal notes, use templates/personal-state.md (gitignored, private).
|
|
226
|
+
# The admin state file at ADMIN_STATE is the non-repo catch-all (email, admin, misc).
|
|
227
|
+
|
|
228
|
+
header "Admin state file"
|
|
229
|
+
|
|
230
|
+
if [ ! -f "$ADMIN_STATE" ]; then
|
|
231
|
+
run cp "$SCRIPT_DIR/templates/repo-state.md" "$ADMIN_STATE"
|
|
232
|
+
log "Created $ADMIN_STATE"
|
|
233
|
+
else
|
|
234
|
+
info "Already exists: $ADMIN_STATE — skipped"
|
|
235
|
+
fi
|
|
236
|
+
|
|
237
|
+
# ── Step 3b: Persona configuration ────────────────────────────────────────────
|
|
238
|
+
|
|
239
|
+
header "Persona configuration"
|
|
240
|
+
|
|
241
|
+
case "$TOOL" in
|
|
242
|
+
claude-code) PERSONAS_DIR="$HOME/.claude/team-context" ;;
|
|
243
|
+
*) PERSONAS_DIR="$HOME/.ai-memory/team-context" ;;
|
|
244
|
+
esac
|
|
245
|
+
|
|
246
|
+
PERSONAS_DEST="$PERSONAS_DIR/personas.json"
|
|
247
|
+
if [ ! -f "$PERSONAS_DEST" ]; then
|
|
248
|
+
run mkdir -p "$PERSONAS_DIR"
|
|
249
|
+
run cp "$SCRIPT_DIR/templates/personas.json" "$PERSONAS_DEST"
|
|
250
|
+
log "Created $PERSONAS_DEST (default personas — edit to customize)"
|
|
251
|
+
else
|
|
252
|
+
info "Already exists: $PERSONAS_DEST — skipped (your customizations are preserved)"
|
|
253
|
+
fi
|
|
254
|
+
|
|
255
|
+
# ── Step 4: Tool-specific files ───────────────────────────────────────────────
|
|
256
|
+
|
|
257
|
+
header "Tool-specific files ($TOOL)"
|
|
258
|
+
|
|
259
|
+
case "$TOOL" in
|
|
260
|
+
claude-code)
|
|
261
|
+
# Global CLAUDE.md fragment
|
|
262
|
+
if [ -n "$GLOBAL_CLAUDE_MD" ]; then
|
|
263
|
+
if [ "$DRY_RUN" = true ]; then
|
|
264
|
+
info "[dry-run] Would append Session State Protocol to $GLOBAL_CLAUDE_MD"
|
|
265
|
+
else
|
|
266
|
+
touch "$GLOBAL_CLAUDE_MD"
|
|
267
|
+
append_if_missing "Session State Protocol" \
|
|
268
|
+
"$(cat "$SPEC_DIR/CLAUDE.md-global-fragment.md")" \
|
|
269
|
+
"$GLOBAL_CLAUDE_MD"
|
|
270
|
+
fi
|
|
271
|
+
fi
|
|
272
|
+
|
|
273
|
+
# Hook scripts
|
|
274
|
+
HOOK_DEST="$HOME/.claude/hooks/check-global-state.sh"
|
|
275
|
+
if [ ! -f "$HOOK_DEST" ] || [ "$UPDATE" = true ]; then
|
|
276
|
+
run cp "$SPEC_DIR/hooks/check-global-state.sh" "$HOOK_DEST"
|
|
277
|
+
run chmod +x "$HOOK_DEST"
|
|
278
|
+
log "Installed hook: $HOOK_DEST"
|
|
279
|
+
else
|
|
280
|
+
info "Hook already exists: $HOOK_DEST — skipped"
|
|
281
|
+
fi
|
|
282
|
+
|
|
283
|
+
SESSION_END_DEST="$HOME/.claude/hooks/session-end.sh"
|
|
284
|
+
if [ ! -f "$SESSION_END_DEST" ] || [ "$UPDATE" = true ]; then
|
|
285
|
+
run cp "$SPEC_DIR/hooks/session-end.sh" "$SESSION_END_DEST"
|
|
286
|
+
run chmod +x "$SESSION_END_DEST"
|
|
287
|
+
log "Installed hook: $SESSION_END_DEST"
|
|
288
|
+
else
|
|
289
|
+
info "Hook already exists: $SESSION_END_DEST — skipped"
|
|
290
|
+
fi
|
|
291
|
+
|
|
292
|
+
# init-memory command
|
|
293
|
+
CMD_DEST="$HOME/.claude/commands/init-memory.md"
|
|
294
|
+
if [ ! -f "$CMD_DEST" ] || [ "$UPDATE" = true ]; then
|
|
295
|
+
run cp "$SPEC_DIR/commands/init-memory.md" "$CMD_DEST"
|
|
296
|
+
log "Installed command: /init-memory"
|
|
297
|
+
else
|
|
298
|
+
info "/init-memory command already exists — skipped"
|
|
299
|
+
fi
|
|
300
|
+
|
|
301
|
+
# doctor command
|
|
302
|
+
DOCTOR_CMD_DEST="$HOME/.claude/commands/doctor.md"
|
|
303
|
+
if [ ! -f "$DOCTOR_CMD_DEST" ] || [ "$UPDATE" = true ]; then
|
|
304
|
+
run cp "$SPEC_DIR/commands/doctor.md" "$DOCTOR_CMD_DEST"
|
|
305
|
+
log "Installed command: /doctor"
|
|
306
|
+
else
|
|
307
|
+
info "/doctor command already exists — skipped"
|
|
308
|
+
fi
|
|
309
|
+
|
|
310
|
+
# doctor.sh script
|
|
311
|
+
run mkdir -p "$HOME/.claude/team-context"
|
|
312
|
+
DOCTOR_DEST="$HOME/.claude/team-context/doctor.sh"
|
|
313
|
+
if [ ! -f "$DOCTOR_DEST" ] || [ "$UPDATE" = true ]; then
|
|
314
|
+
run cp "$SCRIPT_DIR/doctor.sh" "$DOCTOR_DEST"
|
|
315
|
+
run chmod +x "$DOCTOR_DEST"
|
|
316
|
+
log "Installed doctor script: $DOCTOR_DEST"
|
|
317
|
+
else
|
|
318
|
+
info "doctor.sh already exists: $DOCTOR_DEST — skipped"
|
|
319
|
+
fi
|
|
320
|
+
|
|
321
|
+
# journal-summary.sh script
|
|
322
|
+
JOURNAL_DEST="$HOME/.claude/team-context/journal-summary.sh"
|
|
323
|
+
if [ ! -f "$JOURNAL_DEST" ] || [ "$UPDATE" = true ]; then
|
|
324
|
+
run cp "$SCRIPT_DIR/journal-summary.sh" "$JOURNAL_DEST"
|
|
325
|
+
run chmod +x "$JOURNAL_DEST"
|
|
326
|
+
log "Installed journal summary script: $JOURNAL_DEST"
|
|
327
|
+
else
|
|
328
|
+
info "journal-summary.sh already exists: $JOURNAL_DEST — skipped"
|
|
329
|
+
fi
|
|
330
|
+
|
|
331
|
+
# /journal command
|
|
332
|
+
JOURNAL_CMD_DEST="$HOME/.claude/commands/journal.md"
|
|
333
|
+
if [ ! -f "$JOURNAL_CMD_DEST" ] || [ "$UPDATE" = true ]; then
|
|
334
|
+
run cp "$SPEC_DIR/commands/journal.md" "$JOURNAL_CMD_DEST"
|
|
335
|
+
log "Installed command: /journal"
|
|
336
|
+
else
|
|
337
|
+
info "/journal command already exists — skipped"
|
|
338
|
+
fi
|
|
339
|
+
|
|
340
|
+
# /init-team command
|
|
341
|
+
TEAM_CMD_DEST="$HOME/.claude/commands/init-team.md"
|
|
342
|
+
if [ ! -f "$TEAM_CMD_DEST" ] || [ "$UPDATE" = true ]; then
|
|
343
|
+
if [ -f "$SPEC_DIR/commands/init-team.md" ]; then
|
|
344
|
+
run cp "$SPEC_DIR/commands/init-team.md" "$TEAM_CMD_DEST"
|
|
345
|
+
log "Installed command: /init-team"
|
|
346
|
+
fi
|
|
347
|
+
else
|
|
348
|
+
info "/init-team command already exists — skipped"
|
|
349
|
+
fi
|
|
350
|
+
|
|
351
|
+
# /review-prs command
|
|
352
|
+
REVIEW_CMD_DEST="$HOME/.claude/commands/review-prs.md"
|
|
353
|
+
if [ ! -f "$REVIEW_CMD_DEST" ] || [ "$UPDATE" = true ]; then
|
|
354
|
+
if [ -f "$SPEC_DIR/commands/review-prs.md" ]; then
|
|
355
|
+
run cp "$SPEC_DIR/commands/review-prs.md" "$REVIEW_CMD_DEST"
|
|
356
|
+
log "Installed command: /review-prs"
|
|
357
|
+
fi
|
|
358
|
+
else
|
|
359
|
+
info "/review-prs command already exists — skipped"
|
|
360
|
+
fi
|
|
361
|
+
|
|
362
|
+
# settings.json — merge hooks, don't overwrite
|
|
363
|
+
SETTINGS="$HOME/.claude/settings.json"
|
|
364
|
+
START_HOOK_CMD="bash ~/.claude/hooks/check-global-state.sh"
|
|
365
|
+
STOP_HOOK_CMD="bash ~/.claude/hooks/session-end.sh"
|
|
366
|
+
|
|
367
|
+
if [ ! -f "$SETTINGS" ]; then
|
|
368
|
+
run cp "$SPEC_DIR/settings.json" "$SETTINGS"
|
|
369
|
+
log "Created $SETTINGS with hook registration"
|
|
370
|
+
else
|
|
371
|
+
# Merge both hooks into existing settings.json using Python
|
|
372
|
+
NEEDS_MERGE=false
|
|
373
|
+
grep -q "check-global-state" "$SETTINGS" 2>/dev/null || NEEDS_MERGE=true
|
|
374
|
+
grep -q "session-end" "$SETTINGS" 2>/dev/null || NEEDS_MERGE=true
|
|
375
|
+
if [ "$NEEDS_MERGE" = true ]; then
|
|
376
|
+
if [ "$DRY_RUN" = false ]; then
|
|
377
|
+
TMP_SETTINGS="$(mktemp)"
|
|
378
|
+
if python3 - "$SETTINGS" "$START_HOOK_CMD" "$STOP_HOOK_CMD" "$TMP_SETTINGS" <<'PYEOF' 2>/dev/null; then
|
|
379
|
+
import json, sys
|
|
380
|
+
settings_path, start_cmd, stop_cmd, out_path = sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4]
|
|
381
|
+
try:
|
|
382
|
+
with open(settings_path) as f:
|
|
383
|
+
settings = json.load(f)
|
|
384
|
+
except (json.JSONDecodeError, IOError):
|
|
385
|
+
sys.exit(1)
|
|
386
|
+
hooks = settings.setdefault("hooks", {})
|
|
387
|
+
|
|
388
|
+
# Claude Code hook format: each event is an array of {matcher, hooks} groups.
|
|
389
|
+
# Each group has a "matcher" (string, "" = match all) and "hooks" (array of
|
|
390
|
+
# {type, command} objects).
|
|
391
|
+
|
|
392
|
+
def collect_hook_commands(event_arr):
|
|
393
|
+
"""Extract all {type, command, ...} objects from any format variant."""
|
|
394
|
+
cmds = []
|
|
395
|
+
if not isinstance(event_arr, list):
|
|
396
|
+
event_arr = [event_arr] if isinstance(event_arr, dict) else []
|
|
397
|
+
for item in event_arr:
|
|
398
|
+
if not isinstance(item, dict):
|
|
399
|
+
continue
|
|
400
|
+
# Correct format: {matcher, hooks: [{type, command}]}
|
|
401
|
+
if "hooks" in item and isinstance(item["hooks"], list):
|
|
402
|
+
for h in item["hooks"]:
|
|
403
|
+
if isinstance(h, dict) and "type" in h:
|
|
404
|
+
cmds.append(h)
|
|
405
|
+
# Legacy flat format: {type, command} directly in array
|
|
406
|
+
elif "type" in item:
|
|
407
|
+
cmds.append(item)
|
|
408
|
+
return cmds
|
|
409
|
+
|
|
410
|
+
def ensure_hook_cmd(event_name, cmd, extra_fields=None):
|
|
411
|
+
"""Add a hook command to an event, normalizing to correct format."""
|
|
412
|
+
existing = collect_hook_commands(hooks.get(event_name, []))
|
|
413
|
+
if any(h.get("command") == cmd for h in existing):
|
|
414
|
+
return # already present
|
|
415
|
+
entry = {"type": "command", "command": cmd}
|
|
416
|
+
if extra_fields:
|
|
417
|
+
entry.update(extra_fields)
|
|
418
|
+
existing.append(entry)
|
|
419
|
+
# Write back in correct {matcher, hooks} format
|
|
420
|
+
hooks[event_name] = [{"matcher": "", "hooks": existing}]
|
|
421
|
+
|
|
422
|
+
ensure_hook_cmd("SessionStart", start_cmd)
|
|
423
|
+
ensure_hook_cmd("Stop", stop_cmd, {"timeout": 30000})
|
|
424
|
+
|
|
425
|
+
with open(out_path, "w") as f:
|
|
426
|
+
json.dump(settings, f, indent=2)
|
|
427
|
+
f.write("\n")
|
|
428
|
+
PYEOF
|
|
429
|
+
mv "$TMP_SETTINGS" "$SETTINGS"
|
|
430
|
+
log "Merged hooks into existing $SETTINGS"
|
|
431
|
+
else
|
|
432
|
+
rm -f "$TMP_SETTINGS"
|
|
433
|
+
warn "Could not auto-merge hooks into $SETTINGS (malformed JSON or python3 unavailable)."
|
|
434
|
+
warn "Manually add hooks from: $SPEC_DIR/settings.json"
|
|
435
|
+
fi
|
|
436
|
+
else
|
|
437
|
+
info "[dry-run] Would merge hooks into $SETTINGS"
|
|
438
|
+
fi
|
|
439
|
+
else
|
|
440
|
+
info "Hooks already registered in settings.json — skipped"
|
|
441
|
+
fi
|
|
442
|
+
fi
|
|
443
|
+
|
|
444
|
+
# Status line script
|
|
445
|
+
STATUSLINE_DEST="$HOME/.claude/team-context/statusline.sh"
|
|
446
|
+
if [ ! -f "$STATUSLINE_DEST" ] || [ "$UPDATE" = true ]; then
|
|
447
|
+
run cp "$SCRIPT_DIR/templates/statusline.sh" "$STATUSLINE_DEST"
|
|
448
|
+
run chmod +x "$STATUSLINE_DEST"
|
|
449
|
+
log "Installed status line: $STATUSLINE_DEST"
|
|
450
|
+
else
|
|
451
|
+
info "Status line already exists: $STATUSLINE_DEST — skipped"
|
|
452
|
+
fi
|
|
453
|
+
|
|
454
|
+
# Merge statusLine config into settings.json
|
|
455
|
+
if [ -f "$SETTINGS" ] && ! grep -q "statusLine" "$SETTINGS" 2>/dev/null; then
|
|
456
|
+
if [ "$DRY_RUN" = false ]; then
|
|
457
|
+
TMP_SL="$(mktemp)"
|
|
458
|
+
if python3 - "$SETTINGS" "$STATUSLINE_DEST" "$TMP_SL" <<'PYEOF' 2>/dev/null; then
|
|
459
|
+
import json, sys
|
|
460
|
+
settings_path, sl_cmd, out_path = sys.argv[1], sys.argv[2], sys.argv[3]
|
|
461
|
+
try:
|
|
462
|
+
with open(settings_path) as f:
|
|
463
|
+
settings = json.load(f)
|
|
464
|
+
except (json.JSONDecodeError, IOError):
|
|
465
|
+
sys.exit(1)
|
|
466
|
+
if "statusLine" not in settings:
|
|
467
|
+
settings["statusLine"] = {"type": "command", "command": sl_cmd, "padding": 2}
|
|
468
|
+
with open(out_path, "w") as f:
|
|
469
|
+
json.dump(settings, f, indent=2)
|
|
470
|
+
f.write("\n")
|
|
471
|
+
PYEOF
|
|
472
|
+
mv "$TMP_SL" "$SETTINGS"
|
|
473
|
+
log "Added statusLine config to $SETTINGS"
|
|
474
|
+
else
|
|
475
|
+
rm -f "$TMP_SL"
|
|
476
|
+
warn "Could not add statusLine to $SETTINGS (malformed JSON or python3 unavailable)"
|
|
477
|
+
fi
|
|
478
|
+
else
|
|
479
|
+
info "[dry-run] Would add statusLine to $SETTINGS"
|
|
480
|
+
fi
|
|
481
|
+
else
|
|
482
|
+
if [ -f "$SETTINGS" ]; then
|
|
483
|
+
info "statusLine already configured in settings.json — skipped"
|
|
484
|
+
fi
|
|
485
|
+
fi
|
|
486
|
+
;;
|
|
487
|
+
|
|
488
|
+
cursor)
|
|
489
|
+
# Global rules
|
|
490
|
+
run mkdir -p "$HOME/.cursor/rules"
|
|
491
|
+
GLOBAL_RULE="$HOME/.cursor/rules/ai-memory.mdc"
|
|
492
|
+
if [ ! -f "$GLOBAL_RULE" ] || [ "${UPDATE:-false}" = true ]; then
|
|
493
|
+
run cp "$SPEC_DIR/global-rule.mdc" "$GLOBAL_RULE"
|
|
494
|
+
log "Installed global Cursor rule: $GLOBAL_RULE"
|
|
495
|
+
else
|
|
496
|
+
info "Global Cursor rule already exists — skipped"
|
|
497
|
+
fi
|
|
498
|
+
|
|
499
|
+
# Per-repo rule (if --repo was passed)
|
|
500
|
+
if [ -n "${REPO_DIR:-}" ]; then
|
|
501
|
+
if [ -d "$REPO_DIR" ]; then
|
|
502
|
+
RULE_DIR="$REPO_DIR/.cursor/rules"
|
|
503
|
+
run mkdir -p "$RULE_DIR"
|
|
504
|
+
if [ ! -f "$RULE_DIR/memory.mdc" ] || [ "${UPDATE:-false}" = true ]; then
|
|
505
|
+
run cp "$SPEC_DIR/repo-rule.mdc" "$RULE_DIR/memory.mdc"
|
|
506
|
+
log "Installed repo rule: $RULE_DIR/memory.mdc"
|
|
507
|
+
warn "Edit $RULE_DIR/memory.mdc to add repo name and initial status"
|
|
508
|
+
else
|
|
509
|
+
info "Repo rule already exists — skipped"
|
|
510
|
+
fi
|
|
511
|
+
else
|
|
512
|
+
warn "--repo path not found or not a directory: $REPO_DIR"
|
|
513
|
+
fi
|
|
514
|
+
fi
|
|
515
|
+
;;
|
|
516
|
+
|
|
517
|
+
generic)
|
|
518
|
+
info "Generic setup complete. See $SPEC_DIR/README.md for system prompt instructions."
|
|
519
|
+
;;
|
|
520
|
+
esac
|
|
521
|
+
|
|
522
|
+
# ── Step 5: Write version file ────────────────────────────────────────────────
|
|
523
|
+
|
|
524
|
+
# If we know the version (passed from install.sh or from package.json), write it
|
|
525
|
+
if [ -n "$WAYFIND_NEW_VERSION" ]; then
|
|
526
|
+
INSTALL_VERSION="$WAYFIND_NEW_VERSION"
|
|
527
|
+
elif [ -f "$SCRIPT_DIR/package.json" ]; then
|
|
528
|
+
INSTALL_VERSION="$(grep '"version"' "$SCRIPT_DIR/package.json" | head -1 | sed 's/.*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/')"
|
|
529
|
+
else
|
|
530
|
+
INSTALL_VERSION=""
|
|
531
|
+
fi
|
|
532
|
+
|
|
533
|
+
if [ -n "$INSTALL_VERSION" ]; then
|
|
534
|
+
KIT_DEST_DIR="$HOME/.claude/team-context"
|
|
535
|
+
VERSION_DEST="$KIT_DEST_DIR/.wayfind-version"
|
|
536
|
+
if [ "$DRY_RUN" = false ]; then
|
|
537
|
+
mkdir -p "$KIT_DEST_DIR"
|
|
538
|
+
TMP_VER="$(mktemp)"
|
|
539
|
+
echo "$INSTALL_VERSION" > "$TMP_VER"
|
|
540
|
+
mv "$TMP_VER" "$VERSION_DEST"
|
|
541
|
+
log "Version v${INSTALL_VERSION} written to $VERSION_DEST"
|
|
542
|
+
else
|
|
543
|
+
info "[dry-run] Would write v${INSTALL_VERSION} to $KIT_DEST_DIR/.wayfind-version"
|
|
544
|
+
fi
|
|
545
|
+
fi
|
|
546
|
+
|
|
547
|
+
# ── Step 6: Inject elicitation prompts into existing team-state files ────────
|
|
548
|
+
|
|
549
|
+
if [ "$UPDATE" = true ] && [ "$TOOL" = "claude-code" ]; then
|
|
550
|
+
header "Elicitation prompts"
|
|
551
|
+
|
|
552
|
+
ELICITATION_MARKER="## Elicitation Prompts"
|
|
553
|
+
ELICITATION_BLOCK="$ELICITATION_MARKER
|
|
554
|
+
|
|
555
|
+
<!-- These prompts guide the AI to capture richer context at decision moments.
|
|
556
|
+
The AI should ask AT MOST ONE of these when a significant decision is stated
|
|
557
|
+
without reasoning. Do not ask during routine implementation — only at moments
|
|
558
|
+
where a choice was made between alternatives.
|
|
559
|
+
|
|
560
|
+
The answers aren't for you (you already know) — they're for your teammates
|
|
561
|
+
who will read the digest tomorrow. -->
|
|
562
|
+
|
|
563
|
+
When a technical or product decision is made without stated reasoning, ask one of:
|
|
564
|
+
- \"What alternatives did you consider?\"
|
|
565
|
+
- \"What constraint or requirement drove this choice?\"
|
|
566
|
+
- \"What would need to change for you to reverse this decision?\"
|
|
567
|
+
- \"Who else on the team does this affect, and how?\"
|
|
568
|
+
- \"What's the risk if this assumption is wrong?\"
|
|
569
|
+
|
|
570
|
+
Do not ask if the decision already includes reasoning, tradeoffs, or constraints.
|
|
571
|
+
Do not ask more than once per decision. Do not ask during routine implementation."
|
|
572
|
+
|
|
573
|
+
# Scan repos for team-state.md files and inject if missing
|
|
574
|
+
INJECT_COUNT=0
|
|
575
|
+
for state_file in $(find "$HOME/repos" -path '*/.claude/team-state.md' 2>/dev/null); do
|
|
576
|
+
if ! grep -qF "$ELICITATION_MARKER" "$state_file" 2>/dev/null; then
|
|
577
|
+
if [ "$DRY_RUN" = false ]; then
|
|
578
|
+
# Insert before "## Shared Gotchas" if it exists, otherwise append
|
|
579
|
+
if grep -qF "## Shared Gotchas" "$state_file" 2>/dev/null; then
|
|
580
|
+
TMP_STATE="$(mktemp)"
|
|
581
|
+
awk -v block="$ELICITATION_BLOCK" '
|
|
582
|
+
/^## Shared Gotchas/ { print block; print ""; }
|
|
583
|
+
{ print }
|
|
584
|
+
' "$state_file" > "$TMP_STATE"
|
|
585
|
+
mv "$TMP_STATE" "$state_file"
|
|
586
|
+
else
|
|
587
|
+
echo "" >> "$state_file"
|
|
588
|
+
echo "$ELICITATION_BLOCK" >> "$state_file"
|
|
589
|
+
fi
|
|
590
|
+
log "Injected elicitation prompts: $state_file"
|
|
591
|
+
INJECT_COUNT=$((INJECT_COUNT + 1))
|
|
592
|
+
else
|
|
593
|
+
info "[dry-run] Would inject elicitation prompts into $state_file"
|
|
594
|
+
fi
|
|
595
|
+
else
|
|
596
|
+
info "Already has elicitation: $(basename "$(dirname "$(dirname "$state_file")")")/$(basename "$(dirname "$state_file")") — skipped"
|
|
597
|
+
fi
|
|
598
|
+
done
|
|
599
|
+
|
|
600
|
+
if [ "$INJECT_COUNT" -eq 0 ] && [ "$DRY_RUN" = false ]; then
|
|
601
|
+
info "No team-state.md files needed elicitation injection"
|
|
602
|
+
fi
|
|
603
|
+
fi
|
|
604
|
+
|
|
605
|
+
# ── Step 7: Personas (informational) ─────────────────────────────────────────
|
|
606
|
+
|
|
607
|
+
header "Personas"
|
|
608
|
+
|
|
609
|
+
echo ""
|
|
610
|
+
echo " Default personas: Product, Design, Engineering, Strategy"
|
|
611
|
+
echo " You can customize personas later with 'wayfind personas'"
|
|
612
|
+
echo ""
|
|
613
|
+
|
|
614
|
+
# ── Step 8: Summary ───────────────────────────────────────────────────────────
|
|
615
|
+
|
|
616
|
+
header "Done"
|
|
617
|
+
|
|
618
|
+
echo ""
|
|
619
|
+
echo "Wayfind installed for $TOOL."
|
|
620
|
+
echo ""
|
|
621
|
+
echo "Next steps:"
|
|
622
|
+
echo " 1. Edit $GLOBAL_STATE with your preferences and projects"
|
|
623
|
+
|
|
624
|
+
if [ "$TOOL" = "claude-code" ]; then
|
|
625
|
+
echo " 2. Open a repo and run /init-memory to set it up"
|
|
626
|
+
echo " 3. Start a Claude Code session — it will load your state automatically"
|
|
627
|
+
echo " 4. Run /journal for a weekly digest of your AI session logs"
|
|
628
|
+
echo " 5. Set up your profile: wayfind whoami --setup"
|
|
629
|
+
echo " 6. Create or join a team: wayfind team create"
|
|
630
|
+
elif [ "$TOOL" = "cursor" ]; then
|
|
631
|
+
echo " 2. Open a repo and run: bash setup.sh --tool cursor --repo <path>"
|
|
632
|
+
echo " 3. Start a Cursor session — the global rule loads your state automatically"
|
|
633
|
+
echo " 4. See specializations/cursor/README.md for full documentation"
|
|
634
|
+
else
|
|
635
|
+
echo " 2. Add the session protocol to your tool's system prompt"
|
|
636
|
+
echo " See: specializations/generic/README.md"
|
|
637
|
+
fi
|
|
638
|
+
|
|
639
|
+
echo ""
|
|
640
|
+
echo " Full documentation: README.md"
|
|
641
|
+
echo ""
|