claude-capsule-kit 3.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/README.md +281 -0
- package/agents/agent-developer.md +206 -0
- package/agents/architecture-explorer.md +90 -0
- package/agents/brainstorm-coordinator.md +120 -0
- package/agents/code-reviewer.md +135 -0
- package/agents/context-librarian.md +227 -0
- package/agents/context-manager.md +151 -0
- package/agents/database-architect.md +107 -0
- package/agents/database-navigator.md +136 -0
- package/agents/debugger.md +121 -0
- package/agents/devops-sre.md +102 -0
- package/agents/error-detective.md +128 -0
- package/agents/git-workflow-manager.md +212 -0
- package/agents/github-issue-tracker.md +252 -0
- package/agents/product-dx-specialist.md +99 -0
- package/agents/refactoring-specialist.md +159 -0
- package/agents/security-engineer.md +102 -0
- package/agents/session-summarizer.md +126 -0
- package/agents/system-architect.md +103 -0
- package/bin/cck.js +1624 -0
- package/commands/crew-setup.md +75 -0
- package/commands/load-session.md +68 -0
- package/commands/sessions.md +55 -0
- package/commands/statusline.md +51 -0
- package/commands/sync-disable.md +35 -0
- package/commands/sync-enable.md +32 -0
- package/commands/sync.md +31 -0
- package/crew/lib/activity-monitor.js +128 -0
- package/crew/lib/crew-config-reader.js +255 -0
- package/crew/lib/health-monitor.js +171 -0
- package/crew/lib/merge-pilot.js +340 -0
- package/crew/lib/prompt-generator.js +268 -0
- package/crew/lib/role-presets.js +63 -0
- package/crew/lib/task-decomposer.js +382 -0
- package/crew/lib/team-spawner.sh +557 -0
- package/crew/lib/team-state-manager.js +155 -0
- package/crew/lib/worktree-gc.js +357 -0
- package/crew/lib/worktree-manager.sh +700 -0
- package/docs/AGENT_ROUTING_GUIDE.md +655 -0
- package/docs/AGENT_TEAMS_WORKTREE_MODE.md +681 -0
- package/docs/BEST_PRACTICES.md +194 -0
- package/docs/CAPSULE_DEGRADATION_RCA.md +577 -0
- package/docs/SKILLS_ORCHESTRATION_ARCHITECTURE.md +455 -0
- package/docs/SUPER_CLAUDE_SYSTEM_ARCHITECTURE.md +1647 -0
- package/docs/TOOL_ENFORCEMENT_REFERENCE.md +418 -0
- package/hooks/check-refresh-needed.sh +77 -0
- package/hooks/detect-changes.sh +90 -0
- package/hooks/keyword-triggers.sh +66 -0
- package/hooks/lib/crew-detect.js +241 -0
- package/hooks/lib/handoff-generator.js +158 -0
- package/hooks/load-from-journal.sh +41 -0
- package/hooks/post-tool-use.js +212 -0
- package/hooks/pre-compact.js +77 -0
- package/hooks/pre-edit-analysis.sh +68 -0
- package/hooks/pre-tool-use.sh +212 -0
- package/hooks/prompt-submit-memory.sh +87 -0
- package/hooks/quality-check.sh +48 -0
- package/hooks/session-end.js +133 -0
- package/hooks/session-start.js +439 -0
- package/hooks/stop.sh +66 -0
- package/hooks/suggest-discoveries.sh +84 -0
- package/hooks/summarize-session.sh +122 -0
- package/hooks/sync-to-journal.sh +77 -0
- package/hooks/sync-todowrite.sh +37 -0
- package/hooks/tool-auto-suggest.sh +77 -0
- package/hooks/user-prompt-submit.sh +71 -0
- package/lib/audit-logger.sh +120 -0
- package/lib/sandbox-validator.sh +194 -0
- package/lib/tool-runner.sh +274 -0
- package/package.json +67 -0
- package/scripts/postinstall.js +4 -0
- package/scripts/show-capsule-visual.sh +103 -0
- package/scripts/show-capsule.sh +113 -0
- package/scripts/show-deps-tree.sh +66 -0
- package/scripts/show-stats-dashboard.sh +52 -0
- package/scripts/show-stats.sh +79 -0
- package/skills/code-review/SKILL.md +520 -0
- package/skills/crew/SKILL.md +395 -0
- package/skills/debug/SKILL.md +473 -0
- package/skills/deep-context/SKILL.md +446 -0
- package/skills/task-router/SKILL.md +390 -0
- package/skills/workflow/SKILL.md +370 -0
- package/templates/CLAUDE.md +124 -0
- package/templates/crew-config.json +21 -0
- package/templates/settings-hooks.json +74 -0
- package/templates/statusline-command.sh +208 -0
- package/tools/context-query/context-query.js +312 -0
- package/tools/context-query/context-query.sh +5 -0
- package/tools/context-query/tool.json +42 -0
- package/tools/dependency-scanner/dependency-scanner.sh +53 -0
- package/tools/dependency-scanner/tool.json +8 -0
- package/tools/find-circular/find-circular.sh +41 -0
- package/tools/find-circular/tool.json +36 -0
- package/tools/find-dead-code/find-dead-code.sh +41 -0
- package/tools/find-dead-code/tool.json +36 -0
- package/tools/impact-analysis/impact-analysis.sh +99 -0
- package/tools/impact-analysis/tool.json +38 -0
- package/tools/progressive-reader/progressive-reader.sh +14 -0
- package/tools/progressive-reader/tool.json +69 -0
- package/tools/query-deps/query-deps.sh +69 -0
- package/tools/query-deps/tool.json +34 -0
- package/tools/stats/stats.js +299 -0
- package/tools/stats/stats.sh +5 -0
- package/tools/stats/tool.json +34 -0
- package/tools/token-counter/README.md +73 -0
- package/tools/token-counter/token-counter.py +202 -0
- package/tools/token-counter/tool.json +40 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* PreCompact Hook - Session Continuity Before Context Compaction
|
|
4
|
+
*
|
|
5
|
+
* Fires right before Claude Code auto-compacts the context window.
|
|
6
|
+
* Saves a rich handoff document to capsule.db so the post-compaction
|
|
7
|
+
* session can resume with full context of what was happening.
|
|
8
|
+
*
|
|
9
|
+
* This is the most critical moment to save state — we still have
|
|
10
|
+
* full context but it's about to be wiped.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { Blink } from 'blink-query';
|
|
14
|
+
import { createInterface } from 'readline';
|
|
15
|
+
import { execSync } from 'child_process';
|
|
16
|
+
import { getCapsuleDbPath, getCrewIdentity, crewNamespace, getProjectHash, isDisabled } from './lib/crew-detect.js';
|
|
17
|
+
import { generateHandoff } from './lib/handoff-generator.js';
|
|
18
|
+
|
|
19
|
+
function getCurrentBranch() {
|
|
20
|
+
try {
|
|
21
|
+
return execSync('git rev-parse --abbrev-ref HEAD', {
|
|
22
|
+
encoding: 'utf-8',
|
|
23
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
24
|
+
}).trim();
|
|
25
|
+
} catch {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function main() {
|
|
31
|
+
try {
|
|
32
|
+
const rl = createInterface({ input: process.stdin });
|
|
33
|
+
let inputJson = '';
|
|
34
|
+
for await (const line of rl) {
|
|
35
|
+
inputJson += line;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const input = JSON.parse(inputJson);
|
|
39
|
+
const sessionId = input.session_id || 'default';
|
|
40
|
+
if (isDisabled()) process.exit(0);
|
|
41
|
+
|
|
42
|
+
const projectHash = getProjectHash();
|
|
43
|
+
const crewId = getCrewIdentity();
|
|
44
|
+
const dbPath = getCapsuleDbPath();
|
|
45
|
+
const branch = getCurrentBranch();
|
|
46
|
+
|
|
47
|
+
// Generate handoff document while we still have full context
|
|
48
|
+
const handoff = generateHandoff(dbPath, sessionId, crewId, projectHash);
|
|
49
|
+
|
|
50
|
+
const blink = new Blink({ dbPath });
|
|
51
|
+
|
|
52
|
+
// Save pre-compaction handoff (tagged distinctly so session-start can find the latest)
|
|
53
|
+
blink.save({
|
|
54
|
+
namespace: crewNamespace(`session/${sessionId}/handoff`, crewId, projectHash),
|
|
55
|
+
title: `Pre-compact handoff ${sessionId}`,
|
|
56
|
+
summary: handoff,
|
|
57
|
+
type: 'SUMMARY',
|
|
58
|
+
content: {
|
|
59
|
+
sessionId,
|
|
60
|
+
teammateName: crewId?.teammate_name || null,
|
|
61
|
+
branch,
|
|
62
|
+
trigger: 'pre-compact',
|
|
63
|
+
generatedAt: Date.now()
|
|
64
|
+
},
|
|
65
|
+
tags: ['handoff', 'pre-compact', sessionId, ...(crewId ? [crewId.teammate_name] : [])]
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
blink.close();
|
|
69
|
+
process.exit(0);
|
|
70
|
+
|
|
71
|
+
} catch (error) {
|
|
72
|
+
// Never block compaction — fail silently
|
|
73
|
+
process.exit(0);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
main();
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Pre-edit analysis - shows impact analysis before file modifications
|
|
3
|
+
# Auto-runs before significant edits to show which files might be affected
|
|
4
|
+
|
|
5
|
+
set -euo pipefail
|
|
6
|
+
|
|
7
|
+
FILE_PATH="${1:-}"
|
|
8
|
+
|
|
9
|
+
if [ -z "$FILE_PATH" ]; then
|
|
10
|
+
exit 0
|
|
11
|
+
fi
|
|
12
|
+
|
|
13
|
+
DEP_GRAPH="$HOME/.claude/dep-graph.toon"
|
|
14
|
+
|
|
15
|
+
if [ ! -f "$DEP_GRAPH" ]; then
|
|
16
|
+
TOOL_RUNNER_PATH="$HOME/.claude/cck/lib/tool-runner.sh"
|
|
17
|
+
|
|
18
|
+
if [ -f "$TOOL_RUNNER_PATH" ]; then
|
|
19
|
+
source "$TOOL_RUNNER_PATH"
|
|
20
|
+
if command -v run_tool &> /dev/null; then
|
|
21
|
+
run_tool dependency-scanner --path "$(pwd)" --output "$DEP_GRAPH" &> /dev/null || true
|
|
22
|
+
fi
|
|
23
|
+
fi
|
|
24
|
+
fi
|
|
25
|
+
|
|
26
|
+
if [ ! -f "$DEP_GRAPH" ]; then
|
|
27
|
+
exit 0
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
# Source TOON parser
|
|
31
|
+
TOON_PARSER="$HOME/.claude/cck/lib/toon-parser.sh"
|
|
32
|
+
if [ -f "$TOON_PARSER" ]; then
|
|
33
|
+
source "$TOON_PARSER"
|
|
34
|
+
else
|
|
35
|
+
exit 0
|
|
36
|
+
fi
|
|
37
|
+
|
|
38
|
+
# Check if file exists in graph
|
|
39
|
+
if ! toon_file_exists "$DEP_GRAPH" "$FILE_PATH"; then
|
|
40
|
+
exit 0
|
|
41
|
+
fi
|
|
42
|
+
|
|
43
|
+
IMPORTER_COUNT=$(toon_count_importers "$DEP_GRAPH" "$FILE_PATH")
|
|
44
|
+
|
|
45
|
+
if [ "$IMPORTER_COUNT" -gt 0 ]; then
|
|
46
|
+
echo ""
|
|
47
|
+
echo "Impact Analysis for: $(basename "$FILE_PATH")"
|
|
48
|
+
echo " Files that import this: $IMPORTER_COUNT"
|
|
49
|
+
|
|
50
|
+
IMPORTERS=$(toon_get_importers "$DEP_GRAPH" "$FILE_PATH" | head -5)
|
|
51
|
+
|
|
52
|
+
if [ -n "$IMPORTERS" ]; then
|
|
53
|
+
echo " Affected files:"
|
|
54
|
+
echo "$IMPORTERS" | while read -r importer; do
|
|
55
|
+
echo " $(basename "$importer")"
|
|
56
|
+
done
|
|
57
|
+
fi
|
|
58
|
+
|
|
59
|
+
if [ "$IMPORTER_COUNT" -gt 5 ]; then
|
|
60
|
+
echo " ... and $((IMPORTER_COUNT - 5)) more"
|
|
61
|
+
fi
|
|
62
|
+
|
|
63
|
+
echo ""
|
|
64
|
+
echo " Run 'run_tool impact-analysis $FILE_PATH' for full analysis"
|
|
65
|
+
echo ""
|
|
66
|
+
fi
|
|
67
|
+
|
|
68
|
+
exit 0
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# Pre-Tool-Use Hook
|
|
4
|
+
# 1. Warns before redundant file reads to encourage using capsule data
|
|
5
|
+
# 2. Enforces Claude Capsule Kit tools for dependency analysis
|
|
6
|
+
# 3. Auto-logs file access to capsule
|
|
7
|
+
# Runs BEFORE each tool call
|
|
8
|
+
# Claude Code passes arguments via stdin as JSON, NOT positional args
|
|
9
|
+
|
|
10
|
+
set -euo pipefail
|
|
11
|
+
|
|
12
|
+
# Defensive check: Ensure CWD exists (can be invalid if directory was deleted)
|
|
13
|
+
if ! cd "$(pwd 2>/dev/null)" 2>/dev/null; then
|
|
14
|
+
cd "$HOME" 2>/dev/null || exit 0
|
|
15
|
+
fi
|
|
16
|
+
|
|
17
|
+
# CCK opt-out check
|
|
18
|
+
[ -f "$PWD/.cck-disable" ] && exit 0
|
|
19
|
+
|
|
20
|
+
# Read JSON from stdin (Claude Code's hook protocol)
|
|
21
|
+
INPUT_JSON=$(cat)
|
|
22
|
+
|
|
23
|
+
# Extract fields from JSON using python3
|
|
24
|
+
TOOL_NAME=$(echo "$INPUT_JSON" | python3 -c "import sys, json; print(json.load(sys.stdin).get('tool_name', ''))" 2>/dev/null || echo "")
|
|
25
|
+
TOOL_INPUT=$(echo "$INPUT_JSON" | python3 -c "import sys, json; import json as j; print(j.dumps(json.load(sys.stdin).get('tool_input', {})))" 2>/dev/null || echo "{}")
|
|
26
|
+
|
|
27
|
+
# Task tool interception - enforce dependency tools
|
|
28
|
+
if [ "$TOOL_NAME" == "Task" ]; then
|
|
29
|
+
# Extract prompt from Task tool input
|
|
30
|
+
TASK_PROMPT=$(echo "$TOOL_INPUT" | python3 -c "import sys, json; print(json.load(sys.stdin).get('prompt', ''))" 2>/dev/null || echo "")
|
|
31
|
+
|
|
32
|
+
if [ -n "$TASK_PROMPT" ]; then
|
|
33
|
+
# Convert to lowercase for pattern matching
|
|
34
|
+
PROMPT_LOWER=$(echo "$TASK_PROMPT" | tr '[:upper:]' '[:lower:]')
|
|
35
|
+
|
|
36
|
+
# Detect dependency-related queries
|
|
37
|
+
if echo "$PROMPT_LOWER" | grep -qE '(depend|import|require|module.*load|circular.*depend|who.*use|what.*import|find.*import)'; then
|
|
38
|
+
# Output JSON enforcement message (to stderr for informational display)
|
|
39
|
+
cat << 'EOF' >&2
|
|
40
|
+
{"type":"tool-enforcement","category":"dependency-analysis","warning":"Query appears to be about code dependencies","dontUse":{"tool":"Task","reason":"inefficient","issues":["Slower: Scans files one-by-one","Limited: Cannot detect circular dependencies","Expensive: High token usage"]},"useInstead":[{"name":"query-deps","useCase":"what imports X, who uses X","command":"bash $HOME/.claude/cck/tools/query-deps/query-deps.sh <file-path>"},{"name":"impact-analysis","useCase":"what would break if I change X","command":"bash $HOME/.claude/cck/tools/impact-analysis/impact-analysis.sh <file-path>"},{"name":"find-circular","useCase":"circular dependencies","command":"bash $HOME/.claude/cck/tools/find-circular/find-circular.sh"}],"benefit":"These tools are instant and read pre-built dependency graph"}
|
|
41
|
+
EOF
|
|
42
|
+
fi
|
|
43
|
+
|
|
44
|
+
# Detect file search queries
|
|
45
|
+
if echo "$PROMPT_LOWER" | grep -qE '(where.*file|find.*file|locate.*file|search.*file)' && ! echo "$PROMPT_LOWER" | grep -qE '(depend|import|require)'; then
|
|
46
|
+
# Output JSON suggestion message (to stderr for informational display)
|
|
47
|
+
cat << 'EOF' >&2
|
|
48
|
+
{"type":"tool-suggestion","category":"file-search","useInstead":{"tool":"Glob","reason":"faster-and-direct","pattern":"**/*<filename>*","description":"For finding files by name pattern"}}
|
|
49
|
+
EOF
|
|
50
|
+
fi
|
|
51
|
+
|
|
52
|
+
# Context-librarian suggestion for Task spawning (check for past agent findings)
|
|
53
|
+
SUBAGENT_TYPE=$(echo "$TOOL_INPUT" | python3 -c "import sys, json; print(json.load(sys.stdin).get('subagent_type', ''))" 2>/dev/null || echo "")
|
|
54
|
+
|
|
55
|
+
# Skip if already invoking context-librarian
|
|
56
|
+
if [ "$SUBAGENT_TYPE" != "context-librarian" ] && [ -f "$HOME/.claude/cck/state/session_subagents.log" ]; then
|
|
57
|
+
# Extract keywords from task prompt
|
|
58
|
+
KEYWORDS=$(echo "$TASK_PROMPT" | grep -oiE "(auth|database|schema|error|bug|architecture|api|routing|payment)" | head -1)
|
|
59
|
+
|
|
60
|
+
if [ -n "$KEYWORDS" ] && grep -qi "$KEYWORDS" "$HOME/.claude/cck/state/session_subagents.log" 2>/dev/null; then
|
|
61
|
+
cat << EOF >&2
|
|
62
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
63
|
+
💡 PAST AGENT FINDINGS AVAILABLE
|
|
64
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
65
|
+
|
|
66
|
+
Agent: $SUBAGENT_TYPE
|
|
67
|
+
Topic: $KEYWORDS
|
|
68
|
+
|
|
69
|
+
Past findings exist in subagent logs.
|
|
70
|
+
|
|
71
|
+
Suggestion: Query context-librarian first:
|
|
72
|
+
Bash("$HOME/.claude/cck/tools/context-query/context-query.sh search $KEYWORDS")
|
|
73
|
+
|
|
74
|
+
This checks if similar work was already done (saves 30-60s).
|
|
75
|
+
|
|
76
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
77
|
+
EOF
|
|
78
|
+
fi
|
|
79
|
+
fi
|
|
80
|
+
fi
|
|
81
|
+
|
|
82
|
+
exit 0
|
|
83
|
+
fi
|
|
84
|
+
|
|
85
|
+
# Read tool monitoring and large file detection
|
|
86
|
+
if [ "$TOOL_NAME" != "Read" ]; then
|
|
87
|
+
exit 0
|
|
88
|
+
fi
|
|
89
|
+
|
|
90
|
+
FILE_PATH=$(echo "$TOOL_INPUT" | python3 -c "import sys, json; print(json.load(sys.stdin).get('file_path', ''))" 2>/dev/null || echo "")
|
|
91
|
+
|
|
92
|
+
# Find project root via git
|
|
93
|
+
PROJECT_ROOT="$PWD"
|
|
94
|
+
while [ "$PROJECT_ROOT" != "/" ] && [ ! -d "$PROJECT_ROOT/.git" ]; do
|
|
95
|
+
PROJECT_ROOT=$(dirname "$PROJECT_ROOT")
|
|
96
|
+
done
|
|
97
|
+
[ "$PROJECT_ROOT" = "/" ] && PROJECT_ROOT="$PWD"
|
|
98
|
+
|
|
99
|
+
# Check file size and block Read for large files (force progressive-reader)
|
|
100
|
+
if [ -n "$FILE_PATH" ]; then
|
|
101
|
+
RESOLVED_PATH=""
|
|
102
|
+
|
|
103
|
+
# Try multiple path resolutions
|
|
104
|
+
if [ -f "$FILE_PATH" ]; then
|
|
105
|
+
RESOLVED_PATH="$FILE_PATH"
|
|
106
|
+
elif [ -f "$PROJECT_ROOT/$FILE_PATH" ]; then
|
|
107
|
+
RESOLVED_PATH="$PROJECT_ROOT/$FILE_PATH"
|
|
108
|
+
elif [ -f "$(pwd)/$FILE_PATH" ]; then
|
|
109
|
+
RESOLVED_PATH="$(pwd)/$FILE_PATH"
|
|
110
|
+
fi
|
|
111
|
+
|
|
112
|
+
if [ -n "$RESOLVED_PATH" ]; then
|
|
113
|
+
# Skip size check for images and binary files (Claude handles these natively)
|
|
114
|
+
FILE_EXT="${RESOLVED_PATH##*.}"
|
|
115
|
+
FILE_EXT_LOWER=$(echo "$FILE_EXT" | tr '[:upper:]' '[:lower:]')
|
|
116
|
+
|
|
117
|
+
case "$FILE_EXT_LOWER" in
|
|
118
|
+
jpg|jpeg|png|gif|webp|svg|bmp|ico|pdf|ipynb)
|
|
119
|
+
# Allow images, PDFs, notebooks regardless of size
|
|
120
|
+
exit 0
|
|
121
|
+
;;
|
|
122
|
+
esac
|
|
123
|
+
|
|
124
|
+
FILE_SIZE=$(stat -f%z "$RESOLVED_PATH" 2>/dev/null || stat -c%s "$RESOLVED_PATH" 2>/dev/null || echo "0")
|
|
125
|
+
FILE_SIZE_KB=$((FILE_SIZE / 1024))
|
|
126
|
+
|
|
127
|
+
if [ "$FILE_SIZE" -gt 51200 ]; then # 50KB threshold
|
|
128
|
+
echo "{\"decision\": \"block\", \"reason\": \"File ${FILE_SIZE_KB}KB exceeds 50KB. Use progressive-reader instead: \$HOME/.claude/bin/progressive-reader --path $FILE_PATH --list\"}"
|
|
129
|
+
exit 0
|
|
130
|
+
fi
|
|
131
|
+
|
|
132
|
+
# Context-librarian suggestion if file is in capsule
|
|
133
|
+
if [ -f "$HOME/.claude/cck/state/capsule.toon" ] && grep -q "$FILE_PATH" "$HOME/.claude/cck/state/capsule.toon" 2>/dev/null; then
|
|
134
|
+
FILE_AGE=$(grep "$FILE_PATH" "$HOME/.claude/cck/state/capsule.toon" | tail -1 | grep -oE '[0-9]+' | head -1 || echo "0")
|
|
135
|
+
FILE_AGE_MIN=$((FILE_AGE / 60))
|
|
136
|
+
|
|
137
|
+
if [ "$FILE_AGE_MIN" -lt 30 ]; then
|
|
138
|
+
FILE_BASENAME=$(basename "$FILE_PATH" .ts .js .go .py .tsx .jsx)
|
|
139
|
+
cat << EOF >&2
|
|
140
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
141
|
+
💡 CONTEXT AVAILABLE
|
|
142
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
143
|
+
|
|
144
|
+
File: $FILE_PATH
|
|
145
|
+
Status: Already read ${FILE_AGE_MIN}m ago (in capsule)
|
|
146
|
+
|
|
147
|
+
Suggestion: Query context-librarian first (faster, 90% attention):
|
|
148
|
+
Bash("$HOME/.claude/cck/tools/context-query/context-query.sh search $FILE_BASENAME")
|
|
149
|
+
|
|
150
|
+
This retrieves focused context and avoids re-reading ~12,000 tokens.
|
|
151
|
+
|
|
152
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
153
|
+
EOF
|
|
154
|
+
fi
|
|
155
|
+
fi
|
|
156
|
+
fi
|
|
157
|
+
fi
|
|
158
|
+
|
|
159
|
+
if [ -z "$FILE_PATH" ]; then
|
|
160
|
+
exit 0
|
|
161
|
+
fi
|
|
162
|
+
|
|
163
|
+
# Tracking files
|
|
164
|
+
CCK_STATE_DIR="$HOME/.claude/cck/state"
|
|
165
|
+
mkdir -p "$CCK_STATE_DIR"
|
|
166
|
+
RECENT_READS_LOG="$CCK_STATE_DIR/recent_reads.log"
|
|
167
|
+
WARNINGS_SHOWN="$CCK_STATE_DIR/read_warnings_shown.log"
|
|
168
|
+
|
|
169
|
+
# Create logs if they don't exist
|
|
170
|
+
touch "$RECENT_READS_LOG"
|
|
171
|
+
touch "$WARNINGS_SHOWN"
|
|
172
|
+
|
|
173
|
+
# Check if we already warned about this file this session
|
|
174
|
+
if grep -q "^${FILE_PATH}$" "$WARNINGS_SHOWN" 2>/dev/null; then
|
|
175
|
+
exit 0
|
|
176
|
+
fi
|
|
177
|
+
|
|
178
|
+
# Check if file was recently accessed
|
|
179
|
+
CURRENT_TIME=$(date +%s)
|
|
180
|
+
THRESHOLD=300 # 5 minutes in seconds
|
|
181
|
+
|
|
182
|
+
if grep -q "^${FILE_PATH}," "$RECENT_READS_LOG" 2>/dev/null; then
|
|
183
|
+
# Get last read time
|
|
184
|
+
LAST_READ=$(grep "^${FILE_PATH}," "$RECENT_READS_LOG" | tail -1 | cut -d',' -f2)
|
|
185
|
+
TIME_SINCE=$((CURRENT_TIME - LAST_READ))
|
|
186
|
+
|
|
187
|
+
if [ $TIME_SINCE -lt $THRESHOLD ]; then
|
|
188
|
+
# Convert to human readable
|
|
189
|
+
if [ $TIME_SINCE -lt 60 ]; then
|
|
190
|
+
TIME_STR="${TIME_SINCE}s"
|
|
191
|
+
else
|
|
192
|
+
TIME_STR="$((TIME_SINCE / 60))m"
|
|
193
|
+
fi
|
|
194
|
+
|
|
195
|
+
# Show warning as JSON (to stderr so it doesn't interfere with JSON blocking output)
|
|
196
|
+
echo "{\"type\":\"read-warning\",\"file\":\"$FILE_PATH\",\"lastRead\":\"${TIME_STR} ago\",\"message\":\"File recently read - check capsule first\"}" >&2
|
|
197
|
+
|
|
198
|
+
# Mark as warned
|
|
199
|
+
echo "$FILE_PATH" >> "$WARNINGS_SHOWN"
|
|
200
|
+
fi
|
|
201
|
+
fi
|
|
202
|
+
|
|
203
|
+
# Record this read attempt
|
|
204
|
+
echo "$FILE_PATH,$CURRENT_TIME" >> "$RECENT_READS_LOG"
|
|
205
|
+
|
|
206
|
+
# Auto-log file access to capsule
|
|
207
|
+
if [ -x "$HOME/.claude/cck/hooks/log-file-access.sh" ] && [ -x "$HOME/.claude/cck/hooks/log-file-access.sh" ]; then
|
|
208
|
+
# Auto-log this read operation (suppress output to avoid noise)
|
|
209
|
+
"$HOME/.claude/cck/hooks/log-file-access.sh" "$FILE_PATH" "read" > /dev/null 2>&1 || true
|
|
210
|
+
fi
|
|
211
|
+
|
|
212
|
+
exit 0
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# UserPromptSubmit Memory Injection
|
|
3
|
+
# Injects relevant context based on user's prompt
|
|
4
|
+
|
|
5
|
+
set -euo pipefail
|
|
6
|
+
|
|
7
|
+
# Determine script directory for finding tools
|
|
8
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
9
|
+
if [[ "$SCRIPT_DIR" == *".claude/hooks"* ]]; then
|
|
10
|
+
PROJECT_ROOT="$(dirname "$(dirname "$SCRIPT_DIR")")"
|
|
11
|
+
else
|
|
12
|
+
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
|
|
13
|
+
fi
|
|
14
|
+
|
|
15
|
+
MEMORY_DIR="${CLAUDE_MEMORY_DIR:-.claude/memory}"
|
|
16
|
+
TOOLS_DIR="$PROJECT_ROOT/tools/memory-graph"
|
|
17
|
+
QUERY_PY="$TOOLS_DIR/lib/query.py"
|
|
18
|
+
|
|
19
|
+
# Read prompt from stdin
|
|
20
|
+
INPUT_JSON=$(cat)
|
|
21
|
+
PROMPT=$(echo "$INPUT_JSON" | python3 -c "import sys, json; print(json.load(sys.stdin).get('prompt', ''))" 2>/dev/null || echo "")
|
|
22
|
+
|
|
23
|
+
if [ -z "$PROMPT" ]; then
|
|
24
|
+
exit 0
|
|
25
|
+
fi
|
|
26
|
+
|
|
27
|
+
# Check if memory graph exists
|
|
28
|
+
if [ ! -d "$MEMORY_DIR/nodes" ] || [ ! -f "$QUERY_PY" ]; then
|
|
29
|
+
exit 0
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
# Check if graph has nodes
|
|
33
|
+
NODE_COUNT=$(python3 -c "import json; print(json.load(open('$MEMORY_DIR/graph.json')).get('node_count', 0))" 2>/dev/null || echo "0")
|
|
34
|
+
if [ "$NODE_COUNT" = "0" ]; then
|
|
35
|
+
exit 0
|
|
36
|
+
fi
|
|
37
|
+
|
|
38
|
+
CONTEXT=""
|
|
39
|
+
|
|
40
|
+
# Extract potential file paths from prompt (common extensions)
|
|
41
|
+
FILE_MATCHES=$(echo "$PROMPT" | grep -oE '[a-zA-Z0-9_/.-]+\.(ts|tsx|js|jsx|py|go|rs|java|md|sh|json|yaml|yml)' | head -3 || echo "")
|
|
42
|
+
|
|
43
|
+
# Search for file-related knowledge
|
|
44
|
+
for FILE in $FILE_MATCHES; do
|
|
45
|
+
# Convert file path to node ID pattern
|
|
46
|
+
NODE_PATTERN=$(echo "$FILE" | sed 's/[^a-zA-Z0-9]/-/g' | tr '[:upper:]' '[:lower:]')
|
|
47
|
+
|
|
48
|
+
RESULT=$(CLAUDE_MEMORY_DIR="$MEMORY_DIR" python3 "$QUERY_PY" \
|
|
49
|
+
--memory-dir "$MEMORY_DIR" \
|
|
50
|
+
--command search \
|
|
51
|
+
--query "$FILE" \
|
|
52
|
+
--format summary \
|
|
53
|
+
--limit 2 \
|
|
54
|
+
--status active 2>/dev/null || echo "")
|
|
55
|
+
|
|
56
|
+
if [ -n "$RESULT" ]; then
|
|
57
|
+
CONTEXT="$CONTEXT$RESULT"$'\n'
|
|
58
|
+
fi
|
|
59
|
+
done
|
|
60
|
+
|
|
61
|
+
# Extract keywords (simple approach - words 4+ chars)
|
|
62
|
+
KEYWORDS=$(echo "$PROMPT" | tr '[:upper:]' '[:lower:]' | grep -oE '\b[a-z]{4,}\b' | sort -u | head -5 || echo "")
|
|
63
|
+
|
|
64
|
+
# Search for keyword matches in tags
|
|
65
|
+
for KEYWORD in $KEYWORDS; do
|
|
66
|
+
RESULT=$(CLAUDE_MEMORY_DIR="$MEMORY_DIR" python3 "$QUERY_PY" \
|
|
67
|
+
--memory-dir "$MEMORY_DIR" \
|
|
68
|
+
--command tag \
|
|
69
|
+
--query "$KEYWORD" \
|
|
70
|
+
--format summary \
|
|
71
|
+
--limit 2 \
|
|
72
|
+
--status active 2>/dev/null || echo "")
|
|
73
|
+
|
|
74
|
+
if [ -n "$RESULT" ]; then
|
|
75
|
+
CONTEXT="$CONTEXT$RESULT"$'\n'
|
|
76
|
+
fi
|
|
77
|
+
done
|
|
78
|
+
|
|
79
|
+
# Dedupe and output
|
|
80
|
+
if [ -n "$CONTEXT" ]; then
|
|
81
|
+
echo "# Relevant Memory"
|
|
82
|
+
echo ""
|
|
83
|
+
echo "$CONTEXT" | sort -u | head -10
|
|
84
|
+
echo ""
|
|
85
|
+
fi
|
|
86
|
+
|
|
87
|
+
exit 0
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# Quality Check Hook
|
|
4
|
+
# Post-response validation to ensure optimal approach was used
|
|
5
|
+
# Runs AFTER Claude responds but BEFORE sending to user
|
|
6
|
+
|
|
7
|
+
# This hook analyzes the response and provides improvement suggestions
|
|
8
|
+
# It's a learning/reminder tool, not blocking
|
|
9
|
+
|
|
10
|
+
echo ""
|
|
11
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
12
|
+
echo "📊 QUALITY CHECK (Post-Response Validation)"
|
|
13
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
14
|
+
echo ""
|
|
15
|
+
echo "Self-Assessment Checklist:"
|
|
16
|
+
echo ""
|
|
17
|
+
echo "1. ⚡ Parallel Tool Calls"
|
|
18
|
+
echo " ❓ Did I use parallel tool calls for independent operations?"
|
|
19
|
+
echo " 💡 If reading 3+ files → Use multiple Read calls in one message"
|
|
20
|
+
echo ""
|
|
21
|
+
echo "2. 🤖 Sub-Agent Delegation"
|
|
22
|
+
echo " ❓ Should this task have been delegated to a sub-agent?"
|
|
23
|
+
echo " 💡 Complex (>30 min) or specialized (agent dev, Labs, DB) → Delegate"
|
|
24
|
+
echo ""
|
|
25
|
+
echo "3. 🧠 Memory Usage"
|
|
26
|
+
echo " ❓ Did I check exploration journal for continuation work?"
|
|
27
|
+
echo " 💡 'Continue/resume' keywords → Read docs/exploration/ first"
|
|
28
|
+
echo ""
|
|
29
|
+
echo "4. 📝 Progress Tracking"
|
|
30
|
+
echo " ❓ Did I use TodoWrite for multi-step tasks?"
|
|
31
|
+
echo " 💡 3+ steps or >30 min → Track with todos"
|
|
32
|
+
echo ""
|
|
33
|
+
echo "5. 💾 Save Discoveries"
|
|
34
|
+
echo " ❓ Should I save important findings for future sessions?"
|
|
35
|
+
echo " 💡 Discoveries are auto-saved via session handoff on session end"
|
|
36
|
+
echo ""
|
|
37
|
+
echo "6. 🔄 Avoid Redundancy"
|
|
38
|
+
echo " ❓ Did I re-read files I already read recently?"
|
|
39
|
+
echo " 💡 Reference previous reads when possible"
|
|
40
|
+
echo ""
|
|
41
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
42
|
+
echo "✅ If all checked: Response quality is optimal!"
|
|
43
|
+
echo "⚠️ If any unchecked: Consider improvements for next time"
|
|
44
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
45
|
+
echo ""
|
|
46
|
+
|
|
47
|
+
# Always continue (this is informational, not blocking)
|
|
48
|
+
exit 0
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* SessionEnd Hook - Capsule Integration
|
|
4
|
+
*
|
|
5
|
+
* Finalizes the session by creating a session summary record in Capsule.
|
|
6
|
+
* Crew-aware: scopes to teammate namespace and writes to shared DB.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { Blink } from 'blink-query';
|
|
10
|
+
import { createInterface } from 'readline';
|
|
11
|
+
import { existsSync, readFileSync, writeFileSync } from 'fs';
|
|
12
|
+
import { homedir } from 'os';
|
|
13
|
+
import { resolve } from 'path';
|
|
14
|
+
import { execSync } from 'child_process';
|
|
15
|
+
import { getCapsuleDbPath, getCrewIdentity, crewNamespace, getProjectHash, isDisabled } from './lib/crew-detect.js';
|
|
16
|
+
import { generateHandoff } from './lib/handoff-generator.js';
|
|
17
|
+
|
|
18
|
+
function getCurrentBranch() {
|
|
19
|
+
try {
|
|
20
|
+
return execSync('git rev-parse --abbrev-ref HEAD', {
|
|
21
|
+
encoding: 'utf-8',
|
|
22
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
23
|
+
}).trim();
|
|
24
|
+
} catch {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async function main() {
|
|
30
|
+
try {
|
|
31
|
+
// Read hook input from stdin
|
|
32
|
+
const rl = createInterface({ input: process.stdin });
|
|
33
|
+
let inputJson = '';
|
|
34
|
+
|
|
35
|
+
for await (const line of rl) {
|
|
36
|
+
inputJson += line;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const input = JSON.parse(inputJson);
|
|
40
|
+
const sessionId = input.session_id || 'default';
|
|
41
|
+
if (isDisabled()) process.exit(0);
|
|
42
|
+
const projectHash = getProjectHash();
|
|
43
|
+
const currentBranch = getCurrentBranch();
|
|
44
|
+
|
|
45
|
+
// Shared DB in crew mode, local otherwise
|
|
46
|
+
const blink = new Blink({ dbPath: getCapsuleDbPath() });
|
|
47
|
+
const crewId = getCrewIdentity();
|
|
48
|
+
|
|
49
|
+
// Query session activity (from this teammate's namespace)
|
|
50
|
+
const filesNs = crewNamespace(`session/${sessionId}/files`, crewId, projectHash);
|
|
51
|
+
const agentsNs = crewNamespace(`session/${sessionId}/subagents`, crewId, projectHash);
|
|
52
|
+
const sessionFiles = blink.list(filesNs);
|
|
53
|
+
const sessionSubagents = blink.list(agentsNs);
|
|
54
|
+
|
|
55
|
+
// Create session summary
|
|
56
|
+
const teammateSuffix = crewId ? ` (${crewId.teammate_name})` : '';
|
|
57
|
+
const summary = [
|
|
58
|
+
`Session ${sessionId}${teammateSuffix}`,
|
|
59
|
+
`Files accessed: ${sessionFiles.length}`,
|
|
60
|
+
`Sub-agents used: ${sessionSubagents.length}`,
|
|
61
|
+
`Completed at: ${new Date().toISOString()}`
|
|
62
|
+
].join(' | ');
|
|
63
|
+
|
|
64
|
+
// Save SESSION record (META = structured session metadata)
|
|
65
|
+
blink.save({
|
|
66
|
+
namespace: crewNamespace('session', crewId, projectHash),
|
|
67
|
+
title: `Session ${new Date().toISOString()}`,
|
|
68
|
+
summary,
|
|
69
|
+
type: 'META',
|
|
70
|
+
content: {
|
|
71
|
+
sessionId,
|
|
72
|
+
teammateName: crewId?.teammate_name || null,
|
|
73
|
+
branch: currentBranch,
|
|
74
|
+
filesCount: sessionFiles.length,
|
|
75
|
+
subagentsCount: sessionSubagents.length,
|
|
76
|
+
endedAt: Date.now()
|
|
77
|
+
},
|
|
78
|
+
tags: ['session', sessionId, ...(crewId ? [crewId.teammate_name] : [])]
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// Generate and save handoff document (SUMMARY type for direct consumption)
|
|
82
|
+
const handoff = generateHandoff(getCapsuleDbPath(), sessionId, crewId, projectHash);
|
|
83
|
+
blink.save({
|
|
84
|
+
namespace: crewNamespace(`session/${sessionId}/handoff`, crewId, projectHash),
|
|
85
|
+
title: `Session ${sessionId} Handoff`,
|
|
86
|
+
summary: handoff,
|
|
87
|
+
type: 'SUMMARY',
|
|
88
|
+
content: {
|
|
89
|
+
sessionId,
|
|
90
|
+
teammateName: crewId?.teammate_name || null,
|
|
91
|
+
branch: currentBranch,
|
|
92
|
+
generatedAt: Date.now()
|
|
93
|
+
},
|
|
94
|
+
tags: ['handoff', sessionId, ...(crewId ? [crewId.teammate_name] : [])]
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
blink.close();
|
|
98
|
+
|
|
99
|
+
// Update team-state.json when in crew mode
|
|
100
|
+
if (crewId) {
|
|
101
|
+
try {
|
|
102
|
+
const profileName = crewId.profile_name || 'default';
|
|
103
|
+
const baseStateDir = resolve(homedir(), '.claude', 'crew', projectHash);
|
|
104
|
+
const profileStateDir = resolve(baseStateDir, profileName);
|
|
105
|
+
|
|
106
|
+
// Try profile-scoped path first, then legacy flat path
|
|
107
|
+
let statePath = resolve(profileStateDir, 'team-state.json');
|
|
108
|
+
if (!existsSync(statePath)) {
|
|
109
|
+
statePath = resolve(baseStateDir, 'team-state.json');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (existsSync(statePath)) {
|
|
113
|
+
const state = JSON.parse(readFileSync(statePath, 'utf-8'));
|
|
114
|
+
if (state.teammates?.[crewId.teammate_name]) {
|
|
115
|
+
state.teammates[crewId.teammate_name].last_active = new Date().toISOString();
|
|
116
|
+
state.teammates[crewId.teammate_name].status = 'idle';
|
|
117
|
+
state.updated_at = new Date().toISOString();
|
|
118
|
+
writeFileSync(statePath, JSON.stringify(state, null, 2));
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
} catch { /* best effort */ }
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// No output needed for SessionEnd
|
|
125
|
+
process.exit(0);
|
|
126
|
+
|
|
127
|
+
} catch (error) {
|
|
128
|
+
console.error(`[session-end.js] Error: ${error.message}`);
|
|
129
|
+
process.exit(0);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
main();
|