claude-cli-advanced-starter-pack 1.0.11 → 1.0.13

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.
@@ -0,0 +1,222 @@
1
+ /**
2
+ * CCASP Usage Tracking Hook
3
+ *
4
+ * Tracks usage of commands, skills, agents, and hooks for smart merge detection.
5
+ * When updates are available, this data helps identify customized assets that
6
+ * may need careful merging rather than blind replacement.
7
+ *
8
+ * Event: PostToolUse (Skill tool)
9
+ */
10
+
11
+ const fs = require('fs');
12
+ const path = require('path');
13
+
14
+ const USAGE_FILE = '.claude/config/usage-tracking.json';
15
+
16
+ /**
17
+ * Get default usage tracking structure
18
+ */
19
+ function getDefaultTracking() {
20
+ return {
21
+ version: '1.0.0',
22
+ assets: {
23
+ commands: {},
24
+ skills: {},
25
+ agents: {},
26
+ hooks: {},
27
+ },
28
+ _lastModified: new Date().toISOString(),
29
+ };
30
+ }
31
+
32
+ /**
33
+ * Load usage tracking data
34
+ */
35
+ function loadTracking() {
36
+ const trackingPath = path.join(process.cwd(), USAGE_FILE);
37
+
38
+ if (fs.existsSync(trackingPath)) {
39
+ try {
40
+ const data = JSON.parse(fs.readFileSync(trackingPath, 'utf8'));
41
+ return {
42
+ ...getDefaultTracking(),
43
+ ...data,
44
+ assets: {
45
+ commands: {},
46
+ skills: {},
47
+ agents: {},
48
+ hooks: {},
49
+ ...data.assets,
50
+ },
51
+ };
52
+ } catch {
53
+ // Return default
54
+ }
55
+ }
56
+
57
+ return getDefaultTracking();
58
+ }
59
+
60
+ /**
61
+ * Save usage tracking data
62
+ */
63
+ function saveTracking(tracking) {
64
+ const trackingPath = path.join(process.cwd(), USAGE_FILE);
65
+ const trackingDir = path.dirname(trackingPath);
66
+
67
+ if (!fs.existsSync(trackingDir)) {
68
+ fs.mkdirSync(trackingDir, { recursive: true });
69
+ }
70
+
71
+ tracking._lastModified = new Date().toISOString();
72
+ fs.writeFileSync(trackingPath, JSON.stringify(tracking, null, 2), 'utf8');
73
+ }
74
+
75
+ /**
76
+ * Check if an asset file has been customized (differs from template)
77
+ * This is a simple heuristic - checks for user modifications
78
+ */
79
+ function checkIfCustomized(assetType, assetName) {
80
+ try {
81
+ let assetPath;
82
+
83
+ switch (assetType) {
84
+ case 'commands':
85
+ assetPath = path.join(process.cwd(), '.claude', 'commands', `${assetName}.md`);
86
+ break;
87
+ case 'skills':
88
+ assetPath = path.join(process.cwd(), '.claude', 'skills', assetName, 'SKILL.md');
89
+ break;
90
+ case 'agents':
91
+ assetPath = path.join(process.cwd(), '.claude', 'agents', `${assetName}.md`);
92
+ break;
93
+ case 'hooks':
94
+ assetPath = path.join(process.cwd(), '.claude', 'hooks', `${assetName}.js`);
95
+ break;
96
+ default:
97
+ return false;
98
+ }
99
+
100
+ if (!fs.existsSync(assetPath)) {
101
+ return false;
102
+ }
103
+
104
+ // Check for customization markers
105
+ const content = fs.readFileSync(assetPath, 'utf8');
106
+
107
+ // If file contains user customization comment, it's customized
108
+ if (content.includes('<!-- CUSTOMIZED -->') ||
109
+ content.includes('// CUSTOMIZED') ||
110
+ content.includes('# CUSTOMIZED')) {
111
+ return true;
112
+ }
113
+
114
+ // Check file stats - if modified significantly after creation, likely customized
115
+ const stats = fs.statSync(assetPath);
116
+ const modTime = stats.mtime.getTime();
117
+ const birthTime = stats.birthtime?.getTime() || modTime;
118
+
119
+ // If modified more than 1 minute after creation, consider it potentially customized
120
+ // This is a heuristic; the real detection happens during update comparison
121
+ if (modTime - birthTime > 60000) {
122
+ return true;
123
+ }
124
+
125
+ return false;
126
+ } catch {
127
+ return false;
128
+ }
129
+ }
130
+
131
+ /**
132
+ * Track asset usage
133
+ */
134
+ function trackUsage(assetType, assetName) {
135
+ const tracking = loadTracking();
136
+
137
+ if (!tracking.assets[assetType]) {
138
+ tracking.assets[assetType] = {};
139
+ }
140
+
141
+ const existing = tracking.assets[assetType][assetName] || {
142
+ firstUsed: new Date().toISOString(),
143
+ useCount: 0,
144
+ customized: false,
145
+ };
146
+
147
+ // Check if customized (only update if not already marked)
148
+ const isCustomized = existing.customized || checkIfCustomized(assetType, assetName);
149
+
150
+ tracking.assets[assetType][assetName] = {
151
+ ...existing,
152
+ lastUsed: new Date().toISOString(),
153
+ useCount: existing.useCount + 1,
154
+ customized: isCustomized,
155
+ };
156
+
157
+ saveTracking(tracking);
158
+ }
159
+
160
+ /**
161
+ * Extract skill/command name from tool input
162
+ */
163
+ function extractAssetName(toolInput) {
164
+ // For Skill tool, the skill name is in the 'skill' parameter
165
+ if (toolInput.skill) {
166
+ // Handle fully qualified names like "ms-office-suite:pdf"
167
+ const parts = toolInput.skill.split(':');
168
+ return parts[parts.length - 1];
169
+ }
170
+
171
+ return null;
172
+ }
173
+
174
+ /**
175
+ * Main hook handler
176
+ */
177
+ module.exports = async function usageTracking(context) {
178
+ try {
179
+ const { tool_name, tool_input } = context;
180
+
181
+ // Track Skill tool usage
182
+ if (tool_name === 'Skill') {
183
+ const skillName = extractAssetName(tool_input);
184
+ if (skillName) {
185
+ trackUsage('skills', skillName);
186
+ }
187
+ }
188
+
189
+ // Track Read tool usage for .claude/ files (commands, agents, etc.)
190
+ if (tool_name === 'Read' && tool_input.file_path) {
191
+ const filePath = tool_input.file_path;
192
+
193
+ // Check if reading from .claude/ directory
194
+ if (filePath.includes('.claude/')) {
195
+ // Extract asset type and name
196
+ if (filePath.includes('/commands/') && filePath.endsWith('.md')) {
197
+ const name = path.basename(filePath, '.md');
198
+ if (name !== 'INDEX' && name !== 'README') {
199
+ trackUsage('commands', name);
200
+ }
201
+ } else if (filePath.includes('/agents/') && filePath.endsWith('.md')) {
202
+ const name = path.basename(filePath, '.md');
203
+ trackUsage('agents', name);
204
+ } else if (filePath.includes('/skills/') && filePath.includes('SKILL.md')) {
205
+ // Extract skill name from path like .claude/skills/my-skill/SKILL.md
206
+ const match = filePath.match(/\/skills\/([^/]+)\//);
207
+ if (match) {
208
+ trackUsage('skills', match[1]);
209
+ }
210
+ } else if (filePath.includes('/hooks/') && filePath.endsWith('.js')) {
211
+ const name = path.basename(filePath, '.js');
212
+ trackUsage('hooks', name);
213
+ }
214
+ }
215
+ }
216
+ } catch (error) {
217
+ // Silently fail - don't interrupt user workflow
218
+ // console.error('Usage tracking error:', error.message);
219
+ }
220
+
221
+ return { continue: true };
222
+ };