@stackmemoryai/stackmemory 0.3.16 โ 0.3.18
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/README.md +48 -2
- package/dist/cli/commands/skills.js +15 -2
- package/dist/cli/commands/skills.js.map +2 -2
- package/dist/cli/index.js +113 -834
- package/dist/cli/index.js.map +3 -3
- package/dist/core/context/dual-stack-manager.js +1 -1
- package/dist/core/context/dual-stack-manager.js.map +1 -1
- package/dist/core/context/frame-manager.js +3 -0
- package/dist/core/context/frame-manager.js.map +2 -2
- package/dist/integrations/claude-code/subagent-client.js +106 -3
- package/dist/integrations/claude-code/subagent-client.js.map +2 -2
- package/dist/servers/railway/config.js +51 -0
- package/dist/servers/railway/config.js.map +7 -0
- package/dist/servers/railway/index-enhanced.js +156 -0
- package/dist/servers/railway/index-enhanced.js.map +7 -0
- package/dist/servers/railway/minimal.js +48 -3
- package/dist/servers/railway/minimal.js.map +2 -2
- package/dist/servers/railway/storage-test.js +455 -0
- package/dist/servers/railway/storage-test.js.map +7 -0
- package/dist/skills/claude-skills.js +13 -12
- package/dist/skills/claude-skills.js.map +2 -2
- package/dist/skills/recursive-agent-orchestrator.js +27 -18
- package/dist/skills/recursive-agent-orchestrator.js.map +2 -2
- package/dist/skills/unified-rlm-orchestrator.js.map +2 -2
- package/package.json +6 -18
- package/scripts/README-TESTING.md +186 -0
- package/scripts/analyze-cli-security.js +288 -0
- package/scripts/archive/add-phase-tasks-to-linear.js +163 -0
- package/scripts/archive/analyze-linear-duplicates.js +214 -0
- package/scripts/archive/analyze-remaining-duplicates.js +230 -0
- package/scripts/archive/analyze-sta-duplicates.js +292 -0
- package/scripts/archive/analyze-sta-graphql.js +399 -0
- package/scripts/archive/cancel-duplicate-tasks.ts +246 -0
- package/scripts/archive/check-all-duplicates.ts +419 -0
- package/scripts/archive/clean-duplicate-tasks.js +114 -0
- package/scripts/archive/cleanup-duplicate-tasks.ts +286 -0
- package/scripts/archive/create-phase-tasks.js +387 -0
- package/scripts/archive/delete-linear-duplicates.js +182 -0
- package/scripts/archive/delete-remaining-duplicates.js +158 -0
- package/scripts/archive/delete-sta-duplicates.js +201 -0
- package/scripts/archive/delete-sta-oauth.js +201 -0
- package/scripts/archive/export-sta-tasks.js +62 -0
- package/scripts/archive/install-auto-sync.js +266 -0
- package/scripts/archive/install-chromadb-hooks.sh +133 -0
- package/scripts/archive/install-enhanced-clear-hooks.sh +431 -0
- package/scripts/archive/install-post-task-hooks.sh +289 -0
- package/scripts/archive/install-stackmemory-hooks.sh +420 -0
- package/scripts/archive/merge-linear-duplicates-safe.ts +362 -0
- package/scripts/archive/merge-linear-duplicates.ts +180 -0
- package/scripts/archive/remove-sta-tasks.js +70 -0
- package/scripts/archive/setup-background-sync.sh +168 -0
- package/scripts/archive/setup-claude-auto-triggers.sh +181 -0
- package/scripts/archive/setup-claude-autostart.sh +305 -0
- package/scripts/archive/setup-git-hooks.sh +25 -0
- package/scripts/archive/setup-linear-oauth.sh +46 -0
- package/scripts/archive/setup-mcp.sh +113 -0
- package/scripts/archive/setup-railway-deployment.sh +81 -0
- package/scripts/auto-handoff.sh +262 -0
- package/scripts/background-sync-manager.js +416 -0
- package/scripts/benchmark-performance.ts +57 -0
- package/scripts/check-redis.ts +48 -0
- package/scripts/chromadb-auto-loader.sh +128 -0
- package/scripts/chromadb-context-loader.js +479 -0
- package/scripts/claude-chromadb-hook.js +460 -0
- package/scripts/claude-code-wrapper.sh +66 -0
- package/scripts/claude-linear-skill.js +455 -0
- package/scripts/claude-pre-commit.sh +302 -0
- package/scripts/claude-sm-autostart.js +532 -0
- package/scripts/claude-sm-setup.sh +367 -0
- package/scripts/claude-with-chromadb.sh +69 -0
- package/scripts/claude-worktree-manager.sh +323 -0
- package/scripts/claude-worktree-monitor.sh +371 -0
- package/scripts/claude-worktree-setup.sh +327 -0
- package/scripts/clean-linear-backlog.js +273 -0
- package/scripts/cleanup-old-sessions.sh +57 -0
- package/scripts/codex-wrapper.sh +88 -0
- package/scripts/create-sandbox.sh +269 -0
- package/scripts/debug-linear-update.js +174 -0
- package/scripts/delete-linear-tasks.js +167 -0
- package/scripts/deploy.sh +89 -0
- package/scripts/deployment/railway.sh +352 -0
- package/scripts/deployment/test-deployment.js +194 -0
- package/scripts/detect-and-rehydrate.js +162 -0
- package/scripts/detect-and-rehydrate.mjs +165 -0
- package/scripts/development/create-demo-tasks.js +143 -0
- package/scripts/development/debug-frame-test.js +16 -0
- package/scripts/development/demo-auto-sync.js +128 -0
- package/scripts/development/fix-all-imports.js +213 -0
- package/scripts/development/fix-imports.js +229 -0
- package/scripts/development/fix-lint-loop.cjs +103 -0
- package/scripts/development/fix-project-id.ts +161 -0
- package/scripts/development/fix-strict-mode-issues.ts +291 -0
- package/scripts/development/reorganize-structure.sh +228 -0
- package/scripts/development/test-persistence-direct.js +148 -0
- package/scripts/development/test-persistence.js +114 -0
- package/scripts/development/test-tasks.js +93 -0
- package/scripts/development/update-imports.js +212 -0
- package/scripts/fetch-linear-status.js +125 -0
- package/scripts/git-hooks/README.md +310 -0
- package/scripts/git-hooks/branch-context-manager.sh +342 -0
- package/scripts/git-hooks/post-checkout-stackmemory.sh +63 -0
- package/scripts/git-hooks/post-commit-stackmemory.sh +305 -0
- package/scripts/git-hooks/pre-commit-stackmemory.sh +275 -0
- package/scripts/hooks/cleanup-shell.sh +130 -0
- package/scripts/hooks/task-complete.sh +114 -0
- package/scripts/initialize.ts +129 -0
- package/scripts/install-claude-hooks-auto.js +104 -0
- package/scripts/install-claude-hooks.sh +133 -0
- package/scripts/install-global.sh +296 -0
- package/scripts/install.sh +235 -0
- package/scripts/linear-auto-sync.js +262 -0
- package/scripts/linear-auto-sync.sh +161 -0
- package/scripts/linear-sync-daemon.js +150 -0
- package/scripts/linear-task-review.js +237 -0
- package/scripts/list-linear-tasks.ts +178 -0
- package/scripts/mcp-proxy.js +66 -0
- package/scripts/opencode-wrapper.sh +85 -0
- package/scripts/publish-local.js +74 -0
- package/scripts/query-chromadb.ts +201 -0
- package/scripts/railway-env-setup.sh +39 -0
- package/scripts/reconcile-local-tasks.js +170 -0
- package/scripts/recreate-frames-db.js +89 -0
- package/scripts/setup/claude-integration.js +138 -0
- package/scripts/setup/configure-alias.js +125 -0
- package/scripts/setup/configure-codex-alias.js +161 -0
- package/scripts/setup/configure-opencode-alias.js +175 -0
- package/scripts/setup-claude-integration.js +204 -0
- package/scripts/setup-claude-integration.sh +183 -0
- package/scripts/setup.sh +31 -0
- package/scripts/show-linear-summary.ts +172 -0
- package/scripts/stackmemory-auto-handoff.sh +231 -0
- package/scripts/stackmemory-daemon.sh +40 -0
- package/scripts/start-linear-sync-daemon.sh +141 -0
- package/scripts/start-temporal-paradox.sh +214 -0
- package/scripts/status.ts +159 -0
- package/scripts/sync-and-clean-tasks.js +258 -0
- package/scripts/sync-frames-from-railway.js +228 -0
- package/scripts/sync-linear-graphql.js +303 -0
- package/scripts/sync-linear-tasks.js +186 -0
- package/scripts/test-auto-triggers.sh +57 -0
- package/scripts/test-browser-mcp.js +74 -0
- package/scripts/test-chromadb-full.js +115 -0
- package/scripts/test-chromadb-hooks.sh +28 -0
- package/scripts/test-chromadb-sync.ts +245 -0
- package/scripts/test-cli-security.js +293 -0
- package/scripts/test-hooks-persistence.sh +220 -0
- package/scripts/test-installation-scenarios.sh +359 -0
- package/scripts/test-installation.sh +224 -0
- package/scripts/test-mcp.js +163 -0
- package/scripts/test-pre-publish-quick.sh +75 -0
- package/scripts/test-quality-gates.sh +263 -0
- package/scripts/test-railway-db.js +222 -0
- package/scripts/test-redis-storage.ts +490 -0
- package/scripts/test-rlm-basic.sh +122 -0
- package/scripts/test-rlm-comprehensive.sh +260 -0
- package/scripts/test-rlm-e2e.sh +268 -0
- package/scripts/test-rlm-simple.js +90 -0
- package/scripts/test-rlm.js +110 -0
- package/scripts/test-session-handoff.sh +165 -0
- package/scripts/test-shell-integration.sh +275 -0
- package/scripts/testing/ab-test-runner.ts +508 -0
- package/scripts/testing/collect-metrics.ts +457 -0
- package/scripts/testing/quick-effectiveness-demo.js +187 -0
- package/scripts/testing/real-performance-test.js +422 -0
- package/scripts/testing/run-effectiveness-tests.sh +176 -0
- package/scripts/testing/scripts/testing/ab-test-runner.js +363 -0
- package/scripts/testing/scripts/testing/collect-metrics.js +292 -0
- package/scripts/testing/simple-effectiveness-test.js +310 -0
- package/scripts/testing/src/core/context/context-bridge.js +253 -0
- package/scripts/testing/src/core/context/frame-manager.js +746 -0
- package/scripts/testing/src/core/context/shared-context-layer.js +437 -0
- package/scripts/testing/src/core/database/database-adapter.js +54 -0
- package/scripts/testing/src/core/errors/index.js +291 -0
- package/scripts/testing/src/core/errors/recovery.js +268 -0
- package/scripts/testing/src/core/monitoring/logger.js +145 -0
- package/scripts/testing/src/core/retrieval/context-retriever.js +516 -0
- package/scripts/testing/src/core/session/index.js +1 -0
- package/scripts/testing/src/core/session/session-manager.js +323 -0
- package/scripts/testing/src/core/trace/cli-trace-wrapper.js +140 -0
- package/scripts/testing/src/core/trace/db-trace-wrapper.js +251 -0
- package/scripts/testing/src/core/trace/debug-trace.js +398 -0
- package/scripts/testing/src/core/trace/index.js +120 -0
- package/scripts/testing/src/core/trace/linear-api-wrapper.js +204 -0
- package/scripts/update-linear-status.js +268 -0
- package/scripts/update-linear-tasks-fixed.js +284 -0
- package/templates/claude-hooks/hooks.json +5 -0
- package/templates/claude-hooks/on-clear.js +56 -0
- package/templates/claude-hooks/on-startup.js +56 -0
- package/templates/claude-hooks/tool-use-trace.js +67 -0
- package/dist/features/tui/components/analytics-panel.js +0 -157
- package/dist/features/tui/components/analytics-panel.js.map +0 -7
- package/dist/features/tui/components/frame-visualizer.js +0 -377
- package/dist/features/tui/components/frame-visualizer.js.map +0 -7
- package/dist/features/tui/components/pr-tracker.js +0 -135
- package/dist/features/tui/components/pr-tracker.js.map +0 -7
- package/dist/features/tui/components/session-monitor.js +0 -299
- package/dist/features/tui/components/session-monitor.js.map +0 -7
- package/dist/features/tui/components/subagent-fleet.js +0 -395
- package/dist/features/tui/components/subagent-fleet.js.map +0 -7
- package/dist/features/tui/components/task-board.js +0 -1139
- package/dist/features/tui/components/task-board.js.map +0 -7
- package/dist/features/tui/index.js +0 -408
- package/dist/features/tui/index.js.map +0 -7
- package/dist/features/tui/services/data-service.js +0 -641
- package/dist/features/tui/services/data-service.js.map +0 -7
- package/dist/features/tui/services/linear-task-reader.js +0 -102
- package/dist/features/tui/services/linear-task-reader.js.map +0 -7
- package/dist/features/tui/services/websocket-client.js +0 -162
- package/dist/features/tui/services/websocket-client.js.map +0 -7
- package/dist/features/tui/terminal-compat.js +0 -220
- package/dist/features/tui/terminal-compat.js.map +0 -7
- package/dist/features/tui/types.js +0 -1
- package/dist/features/tui/types.js.map +0 -7
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# Claude-specific Git Worktree Management for Multiple Instances
|
|
4
|
+
# Prevents conflicts when multiple Claude instances work on the same repository
|
|
5
|
+
|
|
6
|
+
set -e
|
|
7
|
+
|
|
8
|
+
# Configuration
|
|
9
|
+
WORKTREE_BASE_DIR="${WORKTREE_BASE_DIR:-../}"
|
|
10
|
+
CLAUDE_INSTANCE_ID="${CLAUDE_INSTANCE_ID:-$(uuidgen | cut -c1-8)}"
|
|
11
|
+
LOCK_DIR=".claude-worktree-locks"
|
|
12
|
+
|
|
13
|
+
# Colors for output
|
|
14
|
+
RED='\033[0;31m'
|
|
15
|
+
GREEN='\033[0;32m'
|
|
16
|
+
YELLOW='\033[1;33m'
|
|
17
|
+
NC='\033[0m' # No Color
|
|
18
|
+
|
|
19
|
+
# Initialize lock directory
|
|
20
|
+
init_locks() {
|
|
21
|
+
mkdir -p "$LOCK_DIR"
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
# Create a Claude-specific worktree with instance isolation
|
|
25
|
+
claude_worktree_create() {
|
|
26
|
+
local branch_base="$1"
|
|
27
|
+
local task_desc="${2:-work}"
|
|
28
|
+
|
|
29
|
+
if [[ -z "$branch_base" ]]; then
|
|
30
|
+
echo -e "${RED}Usage: claude_worktree_create <branch-name-base> [task-description]${NC}"
|
|
31
|
+
return 1
|
|
32
|
+
fi
|
|
33
|
+
|
|
34
|
+
# Generate unique branch name with timestamp and instance ID
|
|
35
|
+
local timestamp=$(date +%Y%m%d-%H%M%S)
|
|
36
|
+
local branch="claude-${branch_base}-${timestamp}-${CLAUDE_INSTANCE_ID}"
|
|
37
|
+
local repo_name="$(basename "$PWD")"
|
|
38
|
+
local worktree_path="${WORKTREE_BASE_DIR}${repo_name}--${branch}"
|
|
39
|
+
|
|
40
|
+
# Create lock file
|
|
41
|
+
init_locks
|
|
42
|
+
local lock_file="${LOCK_DIR}/${branch}.lock"
|
|
43
|
+
|
|
44
|
+
# Check if branch already exists
|
|
45
|
+
if git show-ref --verify --quiet "refs/heads/${branch}"; then
|
|
46
|
+
echo -e "${YELLOW}Branch ${branch} already exists, using existing branch${NC}"
|
|
47
|
+
git worktree add "$worktree_path" "$branch"
|
|
48
|
+
else
|
|
49
|
+
echo -e "${GREEN}Creating worktree: ${worktree_path}${NC}"
|
|
50
|
+
echo -e "${GREEN}Branch: ${branch}${NC}"
|
|
51
|
+
git worktree add -b "$branch" "$worktree_path"
|
|
52
|
+
fi
|
|
53
|
+
|
|
54
|
+
# Write lock information
|
|
55
|
+
cat > "$lock_file" <<EOF
|
|
56
|
+
{
|
|
57
|
+
"instance_id": "${CLAUDE_INSTANCE_ID}",
|
|
58
|
+
"branch": "${branch}",
|
|
59
|
+
"path": "${worktree_path}",
|
|
60
|
+
"created": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
|
|
61
|
+
"task": "${task_desc}",
|
|
62
|
+
"pid": "$$"
|
|
63
|
+
}
|
|
64
|
+
EOF
|
|
65
|
+
|
|
66
|
+
# Copy environment files if they exist
|
|
67
|
+
for file in .env .env.local .mise.toml .tool-versions; do
|
|
68
|
+
if [[ -f "$file" ]]; then
|
|
69
|
+
cp "$file" "$worktree_path/" 2>/dev/null || true
|
|
70
|
+
fi
|
|
71
|
+
done
|
|
72
|
+
|
|
73
|
+
# Trust the directory if mise is available
|
|
74
|
+
if command -v mise &> /dev/null; then
|
|
75
|
+
mise trust "$worktree_path" 2>/dev/null || true
|
|
76
|
+
fi
|
|
77
|
+
|
|
78
|
+
echo -e "${GREEN}Worktree created successfully!${NC}"
|
|
79
|
+
echo "Path: $worktree_path"
|
|
80
|
+
echo "Branch: $branch"
|
|
81
|
+
echo "Instance ID: $CLAUDE_INSTANCE_ID"
|
|
82
|
+
echo
|
|
83
|
+
echo "To switch to this worktree:"
|
|
84
|
+
echo " cd $worktree_path"
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
# List all Claude worktrees with status
|
|
88
|
+
claude_worktree_list() {
|
|
89
|
+
echo -e "${GREEN}=== Claude Worktrees ===${NC}"
|
|
90
|
+
echo
|
|
91
|
+
|
|
92
|
+
git worktree list --porcelain | while IFS= read -r line; do
|
|
93
|
+
if [[ "$line" == worktree* ]]; then
|
|
94
|
+
local path="${line#worktree }"
|
|
95
|
+
local dirname="$(basename "$path")"
|
|
96
|
+
|
|
97
|
+
# Check if this is a Claude worktree
|
|
98
|
+
if [[ "$dirname" == *"--claude-"* ]]; then
|
|
99
|
+
echo -e "${YELLOW}Worktree: ${dirname}${NC}"
|
|
100
|
+
echo " Path: $path"
|
|
101
|
+
|
|
102
|
+
# Read lock file if exists
|
|
103
|
+
local branch="${dirname#*--}"
|
|
104
|
+
local lock_file="${LOCK_DIR}/${branch}.lock"
|
|
105
|
+
if [[ -f "$lock_file" ]]; then
|
|
106
|
+
local instance_id=$(grep '"instance_id"' "$lock_file" | cut -d'"' -f4)
|
|
107
|
+
local task=$(grep '"task"' "$lock_file" | cut -d'"' -f4)
|
|
108
|
+
local created=$(grep '"created"' "$lock_file" | cut -d'"' -f4)
|
|
109
|
+
echo " Instance: $instance_id"
|
|
110
|
+
echo " Task: $task"
|
|
111
|
+
echo " Created: $created"
|
|
112
|
+
fi
|
|
113
|
+
|
|
114
|
+
# Check for uncommitted changes
|
|
115
|
+
if cd "$path" 2>/dev/null; then
|
|
116
|
+
if [[ -n $(git status --porcelain) ]]; then
|
|
117
|
+
echo -e " Status: ${RED}Has uncommitted changes${NC}"
|
|
118
|
+
else
|
|
119
|
+
echo -e " Status: ${GREEN}Clean${NC}"
|
|
120
|
+
fi
|
|
121
|
+
cd - > /dev/null
|
|
122
|
+
fi
|
|
123
|
+
echo
|
|
124
|
+
fi
|
|
125
|
+
fi
|
|
126
|
+
done
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
# Remove a Claude worktree safely
|
|
130
|
+
claude_worktree_remove() {
|
|
131
|
+
local branch="$1"
|
|
132
|
+
|
|
133
|
+
if [[ -z "$branch" ]]; then
|
|
134
|
+
# Try to detect from current directory
|
|
135
|
+
local current_dir="$(basename "$PWD")"
|
|
136
|
+
if [[ "$current_dir" == *"--claude-"* ]]; then
|
|
137
|
+
branch="${current_dir#*--}"
|
|
138
|
+
else
|
|
139
|
+
echo -e "${RED}Usage: claude_worktree_remove <branch-name>${NC}"
|
|
140
|
+
echo "Or run from within a Claude worktree directory"
|
|
141
|
+
return 1
|
|
142
|
+
fi
|
|
143
|
+
fi
|
|
144
|
+
|
|
145
|
+
# Find worktree path
|
|
146
|
+
local worktree_path
|
|
147
|
+
worktree_path=$(git worktree list --porcelain | grep -B1 "branch refs/heads/${branch}" | grep "^worktree" | cut -d' ' -f2)
|
|
148
|
+
|
|
149
|
+
if [[ -z "$worktree_path" ]]; then
|
|
150
|
+
echo -e "${RED}Worktree for branch ${branch} not found${NC}"
|
|
151
|
+
return 1
|
|
152
|
+
fi
|
|
153
|
+
|
|
154
|
+
echo -e "${YELLOW}Removing worktree: ${worktree_path}${NC}"
|
|
155
|
+
echo -e "${YELLOW}Branch: ${branch}${NC}"
|
|
156
|
+
|
|
157
|
+
# Check for uncommitted changes
|
|
158
|
+
if cd "$worktree_path" 2>/dev/null; then
|
|
159
|
+
if [[ -n $(git status --porcelain) ]]; then
|
|
160
|
+
echo -e "${RED}Warning: Worktree has uncommitted changes!${NC}"
|
|
161
|
+
read -p "Continue anyway? (y/N): " -n 1 -r
|
|
162
|
+
echo
|
|
163
|
+
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
|
164
|
+
return 1
|
|
165
|
+
fi
|
|
166
|
+
fi
|
|
167
|
+
cd - > /dev/null
|
|
168
|
+
fi
|
|
169
|
+
|
|
170
|
+
# Remove worktree and branch
|
|
171
|
+
git worktree remove "$worktree_path" --force
|
|
172
|
+
git branch -D "$branch" 2>/dev/null || true
|
|
173
|
+
|
|
174
|
+
# Remove lock file
|
|
175
|
+
rm -f "${LOCK_DIR}/${branch}.lock"
|
|
176
|
+
|
|
177
|
+
echo -e "${GREEN}Worktree removed successfully${NC}"
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
# Clean up old Claude worktrees
|
|
181
|
+
claude_worktree_cleanup() {
|
|
182
|
+
local days="${1:-7}"
|
|
183
|
+
echo -e "${YELLOW}Cleaning up Claude worktrees older than ${days} days...${NC}"
|
|
184
|
+
|
|
185
|
+
local count=0
|
|
186
|
+
git worktree list --porcelain | while IFS= read -r line; do
|
|
187
|
+
if [[ "$line" == worktree* ]]; then
|
|
188
|
+
local path="${line#worktree }"
|
|
189
|
+
local dirname="$(basename "$path")"
|
|
190
|
+
|
|
191
|
+
# Check if this is a Claude worktree
|
|
192
|
+
if [[ "$dirname" == *"--claude-"* ]]; then
|
|
193
|
+
local branch="${dirname#*--}"
|
|
194
|
+
local lock_file="${LOCK_DIR}/${branch}.lock"
|
|
195
|
+
|
|
196
|
+
if [[ -f "$lock_file" ]]; then
|
|
197
|
+
local created=$(grep '"created"' "$lock_file" | cut -d'"' -f4)
|
|
198
|
+
local created_timestamp=$(date -d "$created" +%s 2>/dev/null || date -j -f "%Y-%m-%dT%H:%M:%SZ" "$created" +%s 2>/dev/null)
|
|
199
|
+
local current_timestamp=$(date +%s)
|
|
200
|
+
local age_days=$(( (current_timestamp - created_timestamp) / 86400 ))
|
|
201
|
+
|
|
202
|
+
if [[ $age_days -gt $days ]]; then
|
|
203
|
+
echo " Removing old worktree: $dirname (${age_days} days old)"
|
|
204
|
+
claude_worktree_remove "$branch"
|
|
205
|
+
((count++))
|
|
206
|
+
fi
|
|
207
|
+
fi
|
|
208
|
+
fi
|
|
209
|
+
fi
|
|
210
|
+
done
|
|
211
|
+
|
|
212
|
+
echo -e "${GREEN}Cleaned up ${count} old worktrees${NC}"
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
# Sync worktree with main branch
|
|
216
|
+
claude_worktree_sync() {
|
|
217
|
+
local current_branch=$(git rev-parse --abbrev-ref HEAD)
|
|
218
|
+
|
|
219
|
+
if [[ ! "$current_branch" == claude-* ]]; then
|
|
220
|
+
echo -e "${RED}Not in a Claude worktree branch${NC}"
|
|
221
|
+
return 1
|
|
222
|
+
fi
|
|
223
|
+
|
|
224
|
+
echo -e "${YELLOW}Syncing with main branch...${NC}"
|
|
225
|
+
|
|
226
|
+
# Stash any local changes
|
|
227
|
+
local stash_result=$(git stash)
|
|
228
|
+
|
|
229
|
+
# Fetch and merge/rebase with main
|
|
230
|
+
git fetch origin
|
|
231
|
+
git rebase origin/main || git rebase origin/master || {
|
|
232
|
+
echo -e "${RED}Failed to rebase. You may need to resolve conflicts.${NC}"
|
|
233
|
+
if [[ "$stash_result" != "No local changes to save" ]]; then
|
|
234
|
+
git stash pop
|
|
235
|
+
fi
|
|
236
|
+
return 1
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
# Pop stash if we stashed anything
|
|
240
|
+
if [[ "$stash_result" != "No local changes to save" ]]; then
|
|
241
|
+
git stash pop
|
|
242
|
+
fi
|
|
243
|
+
|
|
244
|
+
echo -e "${GREEN}Sync completed successfully${NC}"
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
# Helper function to switch to a Claude worktree
|
|
248
|
+
claude_worktree_switch() {
|
|
249
|
+
local pattern="$1"
|
|
250
|
+
|
|
251
|
+
if [[ -z "$pattern" ]]; then
|
|
252
|
+
echo -e "${RED}Usage: claude_worktree_switch <pattern>${NC}"
|
|
253
|
+
echo "Pattern can be part of branch name or instance ID"
|
|
254
|
+
return 1
|
|
255
|
+
fi
|
|
256
|
+
|
|
257
|
+
local matches=()
|
|
258
|
+
git worktree list --porcelain | while IFS= read -r line; do
|
|
259
|
+
if [[ "$line" == worktree* ]]; then
|
|
260
|
+
local path="${line#worktree }"
|
|
261
|
+
local dirname="$(basename "$path")"
|
|
262
|
+
|
|
263
|
+
if [[ "$dirname" == *"claude"*"$pattern"* ]]; then
|
|
264
|
+
matches+=("$path")
|
|
265
|
+
echo "Found: $path"
|
|
266
|
+
fi
|
|
267
|
+
fi
|
|
268
|
+
done
|
|
269
|
+
|
|
270
|
+
if [[ ${#matches[@]} -eq 1 ]]; then
|
|
271
|
+
cd "${matches[0]}"
|
|
272
|
+
echo -e "${GREEN}Switched to: ${matches[0]}${NC}"
|
|
273
|
+
elif [[ ${#matches[@]} -gt 1 ]]; then
|
|
274
|
+
echo -e "${YELLOW}Multiple matches found. Please be more specific.${NC}"
|
|
275
|
+
else
|
|
276
|
+
echo -e "${RED}No matching worktree found${NC}"
|
|
277
|
+
fi
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
# Main function for CLI usage
|
|
281
|
+
main() {
|
|
282
|
+
local command="$1"
|
|
283
|
+
shift
|
|
284
|
+
|
|
285
|
+
case "$command" in
|
|
286
|
+
create|add)
|
|
287
|
+
claude_worktree_create "$@"
|
|
288
|
+
;;
|
|
289
|
+
list|ls)
|
|
290
|
+
claude_worktree_list
|
|
291
|
+
;;
|
|
292
|
+
remove|rm)
|
|
293
|
+
claude_worktree_remove "$@"
|
|
294
|
+
;;
|
|
295
|
+
cleanup|clean)
|
|
296
|
+
claude_worktree_cleanup "$@"
|
|
297
|
+
;;
|
|
298
|
+
sync)
|
|
299
|
+
claude_worktree_sync
|
|
300
|
+
;;
|
|
301
|
+
switch|cd)
|
|
302
|
+
claude_worktree_switch "$@"
|
|
303
|
+
;;
|
|
304
|
+
*)
|
|
305
|
+
echo "Claude Worktree Manager for Multiple Instances"
|
|
306
|
+
echo
|
|
307
|
+
echo "Usage: $0 <command> [options]"
|
|
308
|
+
echo
|
|
309
|
+
echo "Commands:"
|
|
310
|
+
echo " create <branch-base> [task] - Create a new Claude worktree"
|
|
311
|
+
echo " list - List all Claude worktrees"
|
|
312
|
+
echo " remove [branch] - Remove a Claude worktree"
|
|
313
|
+
echo " cleanup [days] - Remove old worktrees (default: 7 days)"
|
|
314
|
+
echo " sync - Sync current worktree with main branch"
|
|
315
|
+
echo " switch <pattern> - Switch to a Claude worktree"
|
|
316
|
+
echo
|
|
317
|
+
echo "Environment Variables:"
|
|
318
|
+
echo " CLAUDE_INSTANCE_ID - Unique ID for this Claude instance"
|
|
319
|
+
echo " WORKTREE_BASE_DIR - Base directory for worktrees (default: ../)"
|
|
320
|
+
;;
|
|
321
|
+
esac
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
# Run main function if script is executed directly
|
|
325
|
+
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
|
326
|
+
main "$@"
|
|
327
|
+
fi
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
6
|
+
import dotenv from 'dotenv';
|
|
7
|
+
|
|
8
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
|
|
10
|
+
// Load environment variables from .env file first
|
|
11
|
+
dotenv.config({
|
|
12
|
+
path: path.join(__dirname, '..', '.env'),
|
|
13
|
+
override: true // Override to ensure we use the latest key
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
async function queryLinear(query, variables = {}) {
|
|
17
|
+
const response = await fetch('https://api.linear.app/graphql', {
|
|
18
|
+
method: 'POST',
|
|
19
|
+
headers: {
|
|
20
|
+
'Content-Type': 'application/json',
|
|
21
|
+
'Authorization': process.env.LINEAR_API_KEY
|
|
22
|
+
},
|
|
23
|
+
body: JSON.stringify({ query, variables })
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const data = await response.json();
|
|
27
|
+
if (data.errors) {
|
|
28
|
+
throw new Error(data.errors[0].message);
|
|
29
|
+
}
|
|
30
|
+
return data.data;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function cleanLinearBacklog() {
|
|
34
|
+
const apiKey = process.env.LINEAR_API_KEY;
|
|
35
|
+
|
|
36
|
+
if (!apiKey) {
|
|
37
|
+
console.error('โ LINEAR_API_KEY not found in environment');
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
console.log('๐ Analyzing Linear backlog...\n');
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
// Fetch ALL issues (including completed/cancelled)
|
|
45
|
+
const issuesData = await queryLinear(`
|
|
46
|
+
query {
|
|
47
|
+
issues(first: 250, orderBy: updatedAt) {
|
|
48
|
+
nodes {
|
|
49
|
+
id
|
|
50
|
+
identifier
|
|
51
|
+
title
|
|
52
|
+
description
|
|
53
|
+
state {
|
|
54
|
+
name
|
|
55
|
+
type
|
|
56
|
+
}
|
|
57
|
+
priority
|
|
58
|
+
priorityLabel
|
|
59
|
+
team {
|
|
60
|
+
key
|
|
61
|
+
name
|
|
62
|
+
}
|
|
63
|
+
assignee {
|
|
64
|
+
name
|
|
65
|
+
email
|
|
66
|
+
}
|
|
67
|
+
createdAt
|
|
68
|
+
updatedAt
|
|
69
|
+
completedAt
|
|
70
|
+
canceledAt
|
|
71
|
+
url
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
`);
|
|
76
|
+
|
|
77
|
+
const allIssues = issuesData.issues.nodes;
|
|
78
|
+
console.log(`๐ Total issues found: ${allIssues.length}`);
|
|
79
|
+
|
|
80
|
+
// Categorize issues
|
|
81
|
+
const byStatus = {
|
|
82
|
+
completed: [],
|
|
83
|
+
cancelled: [],
|
|
84
|
+
backlog: [],
|
|
85
|
+
unstarted: [],
|
|
86
|
+
started: [],
|
|
87
|
+
inProgress: []
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const duplicates = new Map(); // Track potential duplicates
|
|
91
|
+
const engineeringMeetings = [];
|
|
92
|
+
const testTasks = [];
|
|
93
|
+
|
|
94
|
+
for (const issue of allIssues) {
|
|
95
|
+
// Categorize by status
|
|
96
|
+
if (issue.state.type === 'completed') {
|
|
97
|
+
byStatus.completed.push(issue);
|
|
98
|
+
} else if (issue.state.type === 'canceled' || issue.state.type === 'cancelled') {
|
|
99
|
+
byStatus.cancelled.push(issue);
|
|
100
|
+
} else if (issue.state.type === 'backlog') {
|
|
101
|
+
byStatus.backlog.push(issue);
|
|
102
|
+
} else if (issue.state.type === 'unstarted') {
|
|
103
|
+
byStatus.unstarted.push(issue);
|
|
104
|
+
} else if (issue.state.type === 'started') {
|
|
105
|
+
byStatus.started.push(issue);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Check for duplicates (normalize title for comparison)
|
|
109
|
+
const normalizedTitle = issue.title
|
|
110
|
+
.replace(/^\[[^\]]+\]\s*/, '') // Remove [ENG-XXX] prefix
|
|
111
|
+
.replace(/^\[.*?\]\s*/, '') // Remove priority markers
|
|
112
|
+
.trim()
|
|
113
|
+
.toLowerCase();
|
|
114
|
+
|
|
115
|
+
if (!duplicates.has(normalizedTitle)) {
|
|
116
|
+
duplicates.set(normalizedTitle, []);
|
|
117
|
+
}
|
|
118
|
+
duplicates.get(normalizedTitle).push(issue);
|
|
119
|
+
|
|
120
|
+
// Identify meeting tasks
|
|
121
|
+
if (issue.title.includes('Engineering x') || issue.title.includes('Meeting')) {
|
|
122
|
+
engineeringMeetings.push(issue);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Identify test tasks
|
|
126
|
+
if (issue.title.toLowerCase().includes('test') &&
|
|
127
|
+
(issue.title.includes('test-') || issue.title.includes('TEST]'))) {
|
|
128
|
+
testTasks.push(issue);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Find actual duplicates (same title appearing multiple times)
|
|
133
|
+
const actualDuplicates = [];
|
|
134
|
+
for (const [title, issues] of duplicates.entries()) {
|
|
135
|
+
if (issues.length > 1) {
|
|
136
|
+
actualDuplicates.push({ title, issues });
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Report findings
|
|
141
|
+
console.log('\n๐ Status Breakdown:');
|
|
142
|
+
console.log(` โ
Completed: ${byStatus.completed.length}`);
|
|
143
|
+
console.log(` โ Cancelled: ${byStatus.cancelled.length}`);
|
|
144
|
+
console.log(` ๐ Backlog: ${byStatus.backlog.length}`);
|
|
145
|
+
console.log(` โณ Unstarted: ${byStatus.unstarted.length}`);
|
|
146
|
+
console.log(` ๐ Started: ${byStatus.started.length}`);
|
|
147
|
+
|
|
148
|
+
console.log('\n๐ Issues to Clean:');
|
|
149
|
+
console.log(` ๐๏ธ Engineering Meetings: ${engineeringMeetings.length}`);
|
|
150
|
+
console.log(` ๐งช Test Tasks: ${testTasks.length}`);
|
|
151
|
+
console.log(` ๐ Duplicate Titles: ${actualDuplicates.length}`);
|
|
152
|
+
|
|
153
|
+
// Show duplicates
|
|
154
|
+
if (actualDuplicates.length > 0) {
|
|
155
|
+
console.log('\n๐ Duplicate Issues:');
|
|
156
|
+
for (const dup of actualDuplicates.slice(0, 10)) {
|
|
157
|
+
console.log(`\n "${dup.title}":`);
|
|
158
|
+
for (const issue of dup.issues) {
|
|
159
|
+
const status = issue.state.type === 'completed' ? 'โ
' :
|
|
160
|
+
issue.state.type === 'cancelled' ? 'โ' : 'โณ';
|
|
161
|
+
console.log(` ${status} ${issue.identifier}: ${issue.state.name}`);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
if (actualDuplicates.length > 10) {
|
|
165
|
+
console.log(` ... and ${actualDuplicates.length - 10} more duplicate groups`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Recommend deletions
|
|
170
|
+
const toDelete = [];
|
|
171
|
+
const toArchive = [];
|
|
172
|
+
|
|
173
|
+
// Add completed issues older than 30 days
|
|
174
|
+
const thirtyDaysAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);
|
|
175
|
+
for (const issue of byStatus.completed) {
|
|
176
|
+
if (new Date(issue.completedAt) < thirtyDaysAgo) {
|
|
177
|
+
toArchive.push(issue);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Add all cancelled issues
|
|
182
|
+
toDelete.push(...byStatus.cancelled);
|
|
183
|
+
|
|
184
|
+
// Add test tasks
|
|
185
|
+
toDelete.push(...testTasks.filter(t => t.state.type !== 'started'));
|
|
186
|
+
|
|
187
|
+
// For duplicates, keep the most recent one
|
|
188
|
+
for (const dup of actualDuplicates) {
|
|
189
|
+
const sorted = dup.issues.sort((a, b) =>
|
|
190
|
+
new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()
|
|
191
|
+
);
|
|
192
|
+
// Mark all but the first (most recent) for deletion
|
|
193
|
+
for (let i = 1; i < sorted.length; i++) {
|
|
194
|
+
if (sorted[i].state.type !== 'completed' && sorted[i].state.type !== 'started') {
|
|
195
|
+
toDelete.push(sorted[i]);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Remove duplicates from delete list
|
|
201
|
+
const uniqueToDelete = Array.from(new Set(toDelete.map(i => i.id)))
|
|
202
|
+
.map(id => toDelete.find(i => i.id === id));
|
|
203
|
+
|
|
204
|
+
console.log('\n๐๏ธ Recommended Actions:');
|
|
205
|
+
console.log(` ๐ Archive (completed >30 days): ${toArchive.length}`);
|
|
206
|
+
console.log(` ๐๏ธ Delete (cancelled/test/duplicates): ${uniqueToDelete.length}`);
|
|
207
|
+
|
|
208
|
+
if (toArchive.length > 0) {
|
|
209
|
+
console.log('\n๐ Issues to Archive:');
|
|
210
|
+
for (const issue of toArchive.slice(0, 5)) {
|
|
211
|
+
console.log(` - ${issue.identifier}: ${issue.title}`);
|
|
212
|
+
}
|
|
213
|
+
if (toArchive.length > 5) {
|
|
214
|
+
console.log(` ... and ${toArchive.length - 5} more`);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (uniqueToDelete.length > 0) {
|
|
219
|
+
console.log('\n๐๏ธ Issues to Delete:');
|
|
220
|
+
const deleteByReason = {
|
|
221
|
+
cancelled: uniqueToDelete.filter(i => i.state.type === 'canceled' || i.state.type === 'cancelled'),
|
|
222
|
+
test: uniqueToDelete.filter(i => i.title.toLowerCase().includes('test')),
|
|
223
|
+
duplicate: uniqueToDelete.filter(i =>
|
|
224
|
+
!i.title.toLowerCase().includes('test') &&
|
|
225
|
+
i.state.type !== 'canceled' &&
|
|
226
|
+
i.state.type !== 'cancelled'
|
|
227
|
+
)
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
console.log(` Cancelled: ${deleteByReason.cancelled.length}`);
|
|
231
|
+
console.log(` Test: ${deleteByReason.test.length}`);
|
|
232
|
+
console.log(` Duplicate: ${deleteByReason.duplicate.length}`);
|
|
233
|
+
|
|
234
|
+
// Save deletion list
|
|
235
|
+
const deletionList = uniqueToDelete.map(i => ({
|
|
236
|
+
id: i.id,
|
|
237
|
+
identifier: i.identifier,
|
|
238
|
+
title: i.title,
|
|
239
|
+
state: i.state.type,
|
|
240
|
+
reason: i.state.type === 'canceled' || i.state.type === 'cancelled' ? 'cancelled' :
|
|
241
|
+
i.title.toLowerCase().includes('test') ? 'test' : 'duplicate'
|
|
242
|
+
}));
|
|
243
|
+
|
|
244
|
+
const filename = `linear-cleanup-${new Date().toISOString().split('T')[0]}.json`;
|
|
245
|
+
fs.writeFileSync(
|
|
246
|
+
path.join(__dirname, '..', filename),
|
|
247
|
+
JSON.stringify(deletionList, null, 2)
|
|
248
|
+
);
|
|
249
|
+
console.log(`\n๐พ Deletion list saved to: ${filename}`);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Summary
|
|
253
|
+
console.log('\n๐ Final Summary:');
|
|
254
|
+
const activeIssues = allIssues.filter(i =>
|
|
255
|
+
i.state.type !== 'completed' &&
|
|
256
|
+
i.state.type !== 'canceled' &&
|
|
257
|
+
i.state.type !== 'cancelled'
|
|
258
|
+
);
|
|
259
|
+
console.log(` Current backlog size: ${activeIssues.length}`);
|
|
260
|
+
console.log(` After cleanup: ${activeIssues.length - uniqueToDelete.length}`);
|
|
261
|
+
console.log(` Reduction: ${Math.round((uniqueToDelete.length / activeIssues.length) * 100)}%`);
|
|
262
|
+
|
|
263
|
+
console.log('\n๐ก Next Steps:');
|
|
264
|
+
console.log(' 1. Review the deletion list in the JSON file');
|
|
265
|
+
console.log(' 2. Use Linear\'s bulk operations to archive/delete');
|
|
266
|
+
console.log(' 3. Run the hourly sync daemon: ./scripts/start-linear-sync-daemon.sh start');
|
|
267
|
+
|
|
268
|
+
} catch (error) {
|
|
269
|
+
console.error('โ Error analyzing Linear backlog:', error.message);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
cleanLinearBacklog();
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# StackMemory Session Cleanup Script
|
|
4
|
+
|
|
5
|
+
echo "๐งน StackMemory Session Cleanup"
|
|
6
|
+
echo "=============================="
|
|
7
|
+
|
|
8
|
+
SESSIONS_DIR="$HOME/.stackmemory/sessions"
|
|
9
|
+
DAYS_TO_KEEP=7
|
|
10
|
+
|
|
11
|
+
# Count current sessions
|
|
12
|
+
TOTAL_SESSIONS=$(find "$SESSIONS_DIR" -type f -name "*.json" | wc -l | tr -d ' ')
|
|
13
|
+
echo "๐ Total sessions: $TOTAL_SESSIONS"
|
|
14
|
+
|
|
15
|
+
# Count old sessions
|
|
16
|
+
OLD_SESSIONS=$(find "$SESSIONS_DIR" -type f -name "*.json" -mtime +$DAYS_TO_KEEP | wc -l | tr -d ' ')
|
|
17
|
+
echo "๐๏ธ Sessions older than $DAYS_TO_KEEP days: $OLD_SESSIONS"
|
|
18
|
+
|
|
19
|
+
if [ "$OLD_SESSIONS" -eq 0 ]; then
|
|
20
|
+
echo "โจ No old sessions to clean up!"
|
|
21
|
+
exit 0
|
|
22
|
+
fi
|
|
23
|
+
|
|
24
|
+
# Calculate space used
|
|
25
|
+
SPACE_BEFORE=$(du -sh "$SESSIONS_DIR" | cut -f1)
|
|
26
|
+
echo "๐พ Current space used: $SPACE_BEFORE"
|
|
27
|
+
|
|
28
|
+
# Ask for confirmation
|
|
29
|
+
read -p "โ ๏ธ Remove $OLD_SESSIONS sessions older than $DAYS_TO_KEEP days? (y/n) " -n 1 -r
|
|
30
|
+
echo
|
|
31
|
+
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
|
32
|
+
# Create backup directory
|
|
33
|
+
BACKUP_DIR="$HOME/.stackmemory/session-backups/$(date +%Y%m%d_%H%M%S)"
|
|
34
|
+
mkdir -p "$BACKUP_DIR"
|
|
35
|
+
|
|
36
|
+
echo "๐ฆ Creating backup in $BACKUP_DIR..."
|
|
37
|
+
|
|
38
|
+
# Move old sessions to backup (sample a few for backup)
|
|
39
|
+
find "$SESSIONS_DIR" -type f -name "*.json" -mtime +$DAYS_TO_KEEP -print0 | \
|
|
40
|
+
head -z -n 100 | \
|
|
41
|
+
xargs -0 -I {} cp {} "$BACKUP_DIR/" 2>/dev/null
|
|
42
|
+
|
|
43
|
+
# Remove old sessions
|
|
44
|
+
find "$SESSIONS_DIR" -type f -name "*.json" -mtime +$DAYS_TO_KEEP -delete
|
|
45
|
+
|
|
46
|
+
# Count remaining sessions
|
|
47
|
+
REMAINING=$(find "$SESSIONS_DIR" -type f -name "*.json" | wc -l | tr -d ' ')
|
|
48
|
+
SPACE_AFTER=$(du -sh "$SESSIONS_DIR" | cut -f1)
|
|
49
|
+
|
|
50
|
+
echo "โ
Cleanup complete!"
|
|
51
|
+
echo "๐ Sessions removed: $((TOTAL_SESSIONS - REMAINING))"
|
|
52
|
+
echo "๐ Sessions remaining: $REMAINING"
|
|
53
|
+
echo "๐พ Space after cleanup: $SPACE_AFTER"
|
|
54
|
+
echo "๐ฆ Sample backup saved to: $BACKUP_DIR"
|
|
55
|
+
else
|
|
56
|
+
echo "โ Cleanup cancelled"
|
|
57
|
+
fi
|