claude-cli-advanced-starter-pack 1.0.12 → 1.0.14
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 +527 -345
- package/package.json +1 -1
- package/src/commands/init.js +336 -42
- package/src/commands/test-setup.js +7 -6
- package/src/data/releases.json +102 -5
- package/src/testing/config.js +213 -84
- package/src/utils/smart-merge.js +457 -0
- package/src/utils/version-check.js +213 -0
- package/templates/commands/create-task-list.template.md +332 -17
- package/templates/commands/update-smart.template.md +111 -0
- package/templates/hooks/ccasp-update-check.template.js +74 -0
- package/templates/hooks/usage-tracking.template.js +222 -0
|
@@ -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
|
+
};
|