clavix 7.2.2 → 7.3.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/dist/clean/cleaner.d.ts +56 -0
- package/dist/clean/cleaner.js +266 -0
- package/dist/clean/discovery.d.ts +68 -0
- package/dist/clean/discovery.js +351 -0
- package/dist/clean/guards.d.ts +56 -0
- package/dist/clean/guards.js +182 -0
- package/dist/clean/prompts.d.ts +67 -0
- package/dist/clean/prompts.js +203 -0
- package/dist/clean/scanner.d.ts +54 -0
- package/dist/clean/scanner.js +174 -0
- package/dist/clean/types.d.ts +28 -0
- package/dist/clean/types.js +15 -0
- package/dist/cli/commands/clean.d.ts +13 -0
- package/dist/cli/commands/clean.js +118 -0
- package/dist/templates/skills/improve.md +52 -20
- package/dist/templates/slash-commands/_canonical/archive.md +1 -1
- package/dist/templates/slash-commands/_canonical/implement.md +1 -1
- package/dist/templates/slash-commands/_canonical/improve.md +1 -1
- package/dist/templates/slash-commands/_canonical/plan.md +1 -1
- package/dist/templates/slash-commands/_canonical/prd.md +1 -1
- package/dist/templates/slash-commands/_canonical/refine.md +1 -1
- package/dist/templates/slash-commands/_canonical/review.md +1 -1
- package/dist/templates/slash-commands/_canonical/start.md +1 -1
- package/dist/templates/slash-commands/_canonical/summarize.md +1 -1
- package/dist/templates/slash-commands/_canonical/verify.md +1 -1
- package/dist/utils/schemas.d.ts +32 -32
- package/package.json +1 -1
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Discovery module for finding Clavix artifacts
|
|
3
|
+
*/
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { DocInjector } from '../core/doc-injector.js';
|
|
6
|
+
import { FileSystem } from '../utils/file-system.js';
|
|
7
|
+
import { CLAVIX_BLOCK_START, CLAVIX_BLOCK_END } from '../constants.js';
|
|
8
|
+
import { getKnownBlockFiles } from './guards.js';
|
|
9
|
+
/**
|
|
10
|
+
* Known integration directories that may contain Clavix command files
|
|
11
|
+
*/
|
|
12
|
+
const INTEGRATION_DIRS = [
|
|
13
|
+
'.claude/commands',
|
|
14
|
+
'.claude/commands/clavix',
|
|
15
|
+
'.gemini/commands',
|
|
16
|
+
'.gemini/commands/clavix',
|
|
17
|
+
'.qwen/commands',
|
|
18
|
+
'.qwen/commands/clavix',
|
|
19
|
+
'.llxprt/commands',
|
|
20
|
+
'.llxprt/commands/clavix',
|
|
21
|
+
];
|
|
22
|
+
/**
|
|
23
|
+
* Discovery class for finding Clavix artifacts in a project
|
|
24
|
+
*/
|
|
25
|
+
export class Discovery {
|
|
26
|
+
/**
|
|
27
|
+
* Find all Clavix artifacts in a specific location
|
|
28
|
+
* @param rootPath - The root path to search
|
|
29
|
+
* @returns Array of CleanTarget objects
|
|
30
|
+
*/
|
|
31
|
+
static async findArtifacts(rootPath) {
|
|
32
|
+
const targets = [];
|
|
33
|
+
// 1. Check for .clavix/ directory
|
|
34
|
+
const clavixDir = path.join(rootPath, '.clavix');
|
|
35
|
+
if (await FileSystem.exists(clavixDir)) {
|
|
36
|
+
const size = await this.getDirectorySize(clavixDir);
|
|
37
|
+
targets.push({
|
|
38
|
+
type: 'directory',
|
|
39
|
+
path: path.relative(rootPath, clavixDir) + path.sep,
|
|
40
|
+
description: 'Clavix configuration directory',
|
|
41
|
+
size,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
// 2. Scan known integration directories for clavix-* files
|
|
45
|
+
for (const relativeDir of INTEGRATION_DIRS) {
|
|
46
|
+
const integrationDir = path.join(rootPath, relativeDir);
|
|
47
|
+
const files = await this.findFilesStartingWith(integrationDir, 'clavix-');
|
|
48
|
+
for (const file of files) {
|
|
49
|
+
const size = await this.getFileSize(file);
|
|
50
|
+
targets.push({
|
|
51
|
+
type: 'file',
|
|
52
|
+
path: path.relative(rootPath, file),
|
|
53
|
+
description: this.getFileDescription(file),
|
|
54
|
+
size,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
// Also check for clavix subdirectory itself
|
|
58
|
+
const clavixSubdir = path.join(rootPath, relativeDir);
|
|
59
|
+
if (integrationDir.endsWith('/clavix')) {
|
|
60
|
+
if ((await FileSystem.exists(clavixSubdir)) &&
|
|
61
|
+
path.relative(rootPath, clavixSubdir) !== '.clavix') {
|
|
62
|
+
const size = await this.getDirectorySize(clavixSubdir);
|
|
63
|
+
targets.push({
|
|
64
|
+
type: 'directory',
|
|
65
|
+
path: path.relative(rootPath, clavixSubdir) + path.sep,
|
|
66
|
+
description: 'Clavix commands directory',
|
|
67
|
+
size,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
// 3. Check for <!-- CLAVIX:START --> blocks in known files
|
|
73
|
+
for (const blockFile of getKnownBlockFiles()) {
|
|
74
|
+
const blockFilePath = path.join(rootPath, blockFile);
|
|
75
|
+
if (await FileSystem.exists(blockFilePath)) {
|
|
76
|
+
if (await DocInjector.hasBlock(blockFilePath, CLAVIX_BLOCK_START, CLAVIX_BLOCK_END)) {
|
|
77
|
+
const size = await this.getFileSize(blockFilePath);
|
|
78
|
+
targets.push({
|
|
79
|
+
type: 'block',
|
|
80
|
+
path: path.relative(rootPath, blockFilePath),
|
|
81
|
+
description: `Clavix documentation block in ${blockFile}`,
|
|
82
|
+
size,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
// 4. Scan for using-clavix files in root
|
|
88
|
+
const usingClavixFiles = await this.findFilesStartingWith(rootPath, 'using-clavix');
|
|
89
|
+
for (const file of usingClavixFiles) {
|
|
90
|
+
if (path.dirname(file) === rootPath) {
|
|
91
|
+
const size = await this.getFileSize(file);
|
|
92
|
+
targets.push({
|
|
93
|
+
type: 'file',
|
|
94
|
+
path: path.relative(rootPath, file),
|
|
95
|
+
description: 'Clavix using meta-skill',
|
|
96
|
+
size,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
// 5. Find integration files from config
|
|
101
|
+
const integrations = await this.loadIntegrations();
|
|
102
|
+
const integrationTargets = await this.findIntegrationArtifacts(rootPath, integrations);
|
|
103
|
+
targets.push(...integrationTargets);
|
|
104
|
+
// 6. Check .skills/ directory for project-local skill directories
|
|
105
|
+
const skillsDir = path.join(rootPath, '.skills');
|
|
106
|
+
if (await FileSystem.exists(skillsDir)) {
|
|
107
|
+
const skillDirs = await this.findClavixSkillDirectories(skillsDir);
|
|
108
|
+
for (const skillDir of skillDirs) {
|
|
109
|
+
const size = await this.getDirectorySize(skillDir);
|
|
110
|
+
targets.push({
|
|
111
|
+
type: 'directory',
|
|
112
|
+
path: path.relative(rootPath, skillDir) + path.sep,
|
|
113
|
+
description: `Clavix skill: ${path.basename(skillDir)}`,
|
|
114
|
+
size,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
// Sort targets by type (directories first, then files, then blocks)
|
|
119
|
+
const typePriority = { directory: 0, block: 1, file: 2 };
|
|
120
|
+
targets.sort((a, b) => typePriority[a.type] - typePriority[b.type]);
|
|
121
|
+
return targets;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Load integration configs from integrations.json
|
|
125
|
+
*/
|
|
126
|
+
static async loadIntegrations() {
|
|
127
|
+
const fs = await import('node:fs/promises');
|
|
128
|
+
const configPath = path.join(process.cwd(), 'src/config/integrations.json');
|
|
129
|
+
try {
|
|
130
|
+
const config = JSON.parse(await fs.readFile(configPath, 'utf-8'));
|
|
131
|
+
return config.integrations || [];
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
return [];
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Find integration generated files dynamically
|
|
139
|
+
*/
|
|
140
|
+
static async findIntegrationArtifacts(rootPath, integrations) {
|
|
141
|
+
const targets = [];
|
|
142
|
+
for (const integration of integrations) {
|
|
143
|
+
// Skip global integrations (handled in cleaner.ts)
|
|
144
|
+
if (integration.global)
|
|
145
|
+
continue;
|
|
146
|
+
// Skip universal integrations (they inject into files, handled separately)
|
|
147
|
+
if (integration.type === 'universal')
|
|
148
|
+
continue;
|
|
149
|
+
const targetDir = path.join(rootPath, integration.directory);
|
|
150
|
+
if (!(await FileSystem.exists(targetDir)))
|
|
151
|
+
continue;
|
|
152
|
+
// Find files matching the integration's pattern
|
|
153
|
+
const files = await this.findMatchingFiles(targetDir, integration);
|
|
154
|
+
for (const file of files) {
|
|
155
|
+
const size = await this.getFileSize(file);
|
|
156
|
+
targets.push({
|
|
157
|
+
type: 'file',
|
|
158
|
+
path: path.relative(rootPath, file),
|
|
159
|
+
description: `Clavix (${integration.displayName}): ${path.basename(file)}`,
|
|
160
|
+
size,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return targets;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Find files matching integration pattern
|
|
168
|
+
*/
|
|
169
|
+
static async findMatchingFiles(dirPath, integration) {
|
|
170
|
+
const files = [];
|
|
171
|
+
const fs = await import('node:fs/promises');
|
|
172
|
+
try {
|
|
173
|
+
const dir = await fs.opendir(dirPath);
|
|
174
|
+
for await (const entry of dir) {
|
|
175
|
+
if (!entry.isFile())
|
|
176
|
+
continue;
|
|
177
|
+
if (Discovery.fileMatchesIntegration(entry.name, integration)) {
|
|
178
|
+
files.push(path.join(dirPath, entry.name));
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
catch {
|
|
183
|
+
// Directory doesn't exist or can't be read
|
|
184
|
+
}
|
|
185
|
+
return files;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Check if filename matches integration's pattern
|
|
189
|
+
*/
|
|
190
|
+
static fileMatchesIntegration(filename, integration) {
|
|
191
|
+
const { filenamePattern, extension } = integration;
|
|
192
|
+
// Escape special regex characters in the pattern
|
|
193
|
+
const escapedPattern = filenamePattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
194
|
+
// Replace {name} with pattern matching any name
|
|
195
|
+
const namePattern = escapedPattern.replace('{name}', '.+');
|
|
196
|
+
// Handle patterns without {name} (static filenames like AGENTS)
|
|
197
|
+
const pattern = filenamePattern.includes('{name}')
|
|
198
|
+
? `^${namePattern}\\${extension.replace('.', '\\.')}$`
|
|
199
|
+
: `^${escapedPattern}\\${extension.replace('.', '\\.')}$`;
|
|
200
|
+
return new RegExp(pattern).test(filename);
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Find Clavix skill directories (.skills/clavix-*)
|
|
204
|
+
*/
|
|
205
|
+
static async findClavixSkillDirectories(skillsDir) {
|
|
206
|
+
const dirs = [];
|
|
207
|
+
const fs = await import('node:fs/promises');
|
|
208
|
+
try {
|
|
209
|
+
const dir = await fs.opendir(skillsDir);
|
|
210
|
+
for await (const entry of dir) {
|
|
211
|
+
if (entry.isDirectory() && entry.name.startsWith('clavix-')) {
|
|
212
|
+
dirs.push(path.join(skillsDir, entry.name));
|
|
213
|
+
}
|
|
214
|
+
// Also include using-clavix meta-skill directory
|
|
215
|
+
if (entry.isDirectory() && entry.name === 'using-clavix') {
|
|
216
|
+
dirs.push(path.join(skillsDir, entry.name));
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
catch {
|
|
221
|
+
// Directory doesn't exist
|
|
222
|
+
}
|
|
223
|
+
return dirs;
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Find files starting with a specific prefix in a directory
|
|
227
|
+
* @param dirPath - The directory to search
|
|
228
|
+
* @param prefix - The file prefix to match
|
|
229
|
+
* @returns Array of matching file paths
|
|
230
|
+
*/
|
|
231
|
+
static async findFilesStartingWith(dirPath, prefix) {
|
|
232
|
+
const files = [];
|
|
233
|
+
try {
|
|
234
|
+
if (!(await FileSystem.exists(dirPath))) {
|
|
235
|
+
return files;
|
|
236
|
+
}
|
|
237
|
+
const fs = await import('node:fs/promises');
|
|
238
|
+
const dir = await fs.opendir(dirPath);
|
|
239
|
+
for await (const entry of dir) {
|
|
240
|
+
if (entry.isFile() && entry.name.startsWith(prefix)) {
|
|
241
|
+
files.push(path.join(dirPath, entry.name));
|
|
242
|
+
}
|
|
243
|
+
else if (entry.isDirectory() && entry.name === 'clavix') {
|
|
244
|
+
// Found a nested clavix directory, include it
|
|
245
|
+
files.push(path.join(dirPath, entry.name, path.sep));
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
catch {
|
|
250
|
+
// If we can't read the directory, just return empty
|
|
251
|
+
// This is expected for non-existent or permission-denied directories
|
|
252
|
+
}
|
|
253
|
+
return files;
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Get the size of a file in bytes
|
|
257
|
+
* @param filePath - The file path
|
|
258
|
+
* @returns File size in bytes
|
|
259
|
+
*/
|
|
260
|
+
static async getFileSize(filePath) {
|
|
261
|
+
try {
|
|
262
|
+
const fs = await import('node:fs/promises');
|
|
263
|
+
const stats = await fs.stat(filePath);
|
|
264
|
+
return stats.size;
|
|
265
|
+
}
|
|
266
|
+
catch {
|
|
267
|
+
return 0;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Get the total size of a directory in bytes
|
|
272
|
+
* @param dirPath - The directory path
|
|
273
|
+
* @returns Total size in bytes
|
|
274
|
+
*/
|
|
275
|
+
static async getDirectorySize(dirPath) {
|
|
276
|
+
let totalSize = 0;
|
|
277
|
+
try {
|
|
278
|
+
const fs = await import('node:fs/promises');
|
|
279
|
+
const dir = await fs.opendir(dirPath);
|
|
280
|
+
for await (const entry of dir) {
|
|
281
|
+
const entryPath = path.join(dirPath, entry.name);
|
|
282
|
+
if (entry.isFile()) {
|
|
283
|
+
try {
|
|
284
|
+
const stats = await fs.stat(entryPath);
|
|
285
|
+
totalSize += stats.size;
|
|
286
|
+
}
|
|
287
|
+
catch {
|
|
288
|
+
// Skip files we can't stat
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
else if (entry.isDirectory()) {
|
|
292
|
+
totalSize += await this.getDirectorySize(entryPath);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
catch {
|
|
297
|
+
// If we can't read, just return 0
|
|
298
|
+
}
|
|
299
|
+
return totalSize;
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Get a description for a file based on its name and location
|
|
303
|
+
* @param filePath - The file path
|
|
304
|
+
* @returns A human-readable description
|
|
305
|
+
*/
|
|
306
|
+
static getFileDescription(filePath) {
|
|
307
|
+
const fileName = path.basename(filePath);
|
|
308
|
+
// Command files
|
|
309
|
+
if (fileName.startsWith('clavix-') && fileName.endsWith('.js')) {
|
|
310
|
+
const commandName = fileName.replace('clavix-', '').replace('.js', '');
|
|
311
|
+
return `Clavix command: ${commandName}`;
|
|
312
|
+
}
|
|
313
|
+
if (fileName.startsWith('clavix-') && fileName.endsWith('.ts')) {
|
|
314
|
+
const commandName = fileName.replace('clavix-', '').replace('.ts', '');
|
|
315
|
+
return `Clavix command: ${commandName}`;
|
|
316
|
+
}
|
|
317
|
+
// Generic clavix file
|
|
318
|
+
if (fileName.startsWith('clavix-')) {
|
|
319
|
+
return `Clavix file: ${fileName}`;
|
|
320
|
+
}
|
|
321
|
+
// using-clavix is a meta-skill
|
|
322
|
+
if (fileName.startsWith('using-clavix')) {
|
|
323
|
+
return 'Clavix meta-skill';
|
|
324
|
+
}
|
|
325
|
+
return `Clavix artifact: ${fileName}`;
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Check if a directory is effectively empty (only contains .clavix)
|
|
329
|
+
* Used to determine if we should also remove empty parent directories
|
|
330
|
+
* @param dirPath - The directory to check
|
|
331
|
+
* @returns true if the directory is considered empty for cleanup purposes
|
|
332
|
+
*/
|
|
333
|
+
static async isEmptyClavixParent(dirPath) {
|
|
334
|
+
try {
|
|
335
|
+
const fs = await import('node:fs/promises');
|
|
336
|
+
const dir = await fs.opendir(dirPath);
|
|
337
|
+
let hasOnlyClavix = true;
|
|
338
|
+
for await (const entry of dir) {
|
|
339
|
+
if (entry.name !== '.clavix') {
|
|
340
|
+
hasOnlyClavix = false;
|
|
341
|
+
break;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
return hasOnlyClavix;
|
|
345
|
+
}
|
|
346
|
+
catch {
|
|
347
|
+
return false;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
//# sourceMappingURL=discovery.js.map
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Safety guards for Clavix artifact detection
|
|
3
|
+
* Ensures only Clavix-related items are targeted for deletion
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Check if a filename follows Clavix naming conventions
|
|
7
|
+
* @param filename - The filename to check
|
|
8
|
+
* @returns true if the file appears to be Clavix-generated
|
|
9
|
+
*/
|
|
10
|
+
export declare function isClavixFile(filename: string): boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Check if a directory path is a known Clavix directory
|
|
13
|
+
* @param dirpath - The directory path to check
|
|
14
|
+
* @returns true if the directory is a Clavix-specific directory
|
|
15
|
+
*/
|
|
16
|
+
export declare function isClavixDirectory(dirpath: string): boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Check if a file is known to potentially contain Clavix documentation blocks
|
|
19
|
+
* @param filepath - The file path to check
|
|
20
|
+
* @returns true if the file may contain Clavix blocks
|
|
21
|
+
*/
|
|
22
|
+
export declare function isBlockFile(filepath: string): boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Check if a path should NEVER be deleted (forbidden patterns)
|
|
25
|
+
* @param filepath - The file or directory path to check
|
|
26
|
+
* @returns true if the path should be preserved
|
|
27
|
+
*/
|
|
28
|
+
export declare function isForbiddenPath(filepath: string): boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Comprehensive safety check - determines if a file can be deleted
|
|
31
|
+
* @param filepath - The file path to check
|
|
32
|
+
* @returns true if the file is safe to delete
|
|
33
|
+
*/
|
|
34
|
+
export declare function shouldDeleteFile(filepath: string): boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Expand a path pattern containing ~ to the actual home directory
|
|
37
|
+
* @param pattern - The path pattern to expand
|
|
38
|
+
* @returns The expanded path
|
|
39
|
+
*/
|
|
40
|
+
export declare function expandHomePath(pattern: string): string;
|
|
41
|
+
/**
|
|
42
|
+
* Get the list of known Clavix directory patterns
|
|
43
|
+
* @returns Array of Clavix directory patterns
|
|
44
|
+
*/
|
|
45
|
+
export declare function getClavixDirectoryPatterns(): readonly string[];
|
|
46
|
+
/**
|
|
47
|
+
* Get the list of files that may contain Clavix documentation blocks
|
|
48
|
+
* @returns Array of known block file names
|
|
49
|
+
*/
|
|
50
|
+
export declare function getKnownBlockFiles(): readonly string[];
|
|
51
|
+
/**
|
|
52
|
+
* Get the list of file prefixes that indicate Clavix-generated files
|
|
53
|
+
* @returns Array of Clavix file prefixes
|
|
54
|
+
*/
|
|
55
|
+
export declare function getClavixFilePrefixes(): readonly string[];
|
|
56
|
+
//# sourceMappingURL=guards.d.ts.map
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Safety guards for Clavix artifact detection
|
|
3
|
+
* Ensures only Clavix-related items are targeted for deletion
|
|
4
|
+
*/
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import os from 'node:os';
|
|
7
|
+
/**
|
|
8
|
+
* Clavix-specific patterns - ONLY items matching these are considered safe to delete
|
|
9
|
+
*/
|
|
10
|
+
const CLAVIX_PATTERNS = {
|
|
11
|
+
// Directories that contain only Clavix artifacts
|
|
12
|
+
directories: [
|
|
13
|
+
'.clavix',
|
|
14
|
+
'.claude/commands/clavix',
|
|
15
|
+
'.gemini/commands/clavix',
|
|
16
|
+
'.qwen/commands/clavix',
|
|
17
|
+
'.llxprt/commands/clavix',
|
|
18
|
+
'~/.config/agents/skills/clavix',
|
|
19
|
+
'~/.codex/prompts/clavix',
|
|
20
|
+
'~/.gemini/commands/clavix',
|
|
21
|
+
'~/.qwen/commands/clavix',
|
|
22
|
+
'~/.llxprt/commands/clavix',
|
|
23
|
+
],
|
|
24
|
+
// File prefixes that indicate Clavix-generated files
|
|
25
|
+
filePrefixes: ['clavix-', 'using-clavix'],
|
|
26
|
+
// Content markers for documentation blocks
|
|
27
|
+
markers: ['<!-- CLAVIX:START -->', '<!-- CLAVIX:END -->'],
|
|
28
|
+
// Files that may contain Clavix documentation blocks
|
|
29
|
+
blockFiles: ['AGENTS.md', 'CLAUDE.md', 'OCTO.md', 'WARP.md', '.github/copilot-instructions.md'],
|
|
30
|
+
// Forbidden patterns - never delete files matching these
|
|
31
|
+
forbidden: [
|
|
32
|
+
'package.json',
|
|
33
|
+
'package-lock.json',
|
|
34
|
+
'yarn.lock',
|
|
35
|
+
'pnpm-lock.yaml',
|
|
36
|
+
'.git/',
|
|
37
|
+
'node_modules/',
|
|
38
|
+
'.npm/',
|
|
39
|
+
'.yarn/',
|
|
40
|
+
'README.md',
|
|
41
|
+
'LICENSE',
|
|
42
|
+
'tsconfig.json',
|
|
43
|
+
],
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Check if a filename follows Clavix naming conventions
|
|
47
|
+
* @param filename - The filename to check
|
|
48
|
+
* @returns true if the file appears to be Clavix-generated
|
|
49
|
+
*/
|
|
50
|
+
export function isClavixFile(filename) {
|
|
51
|
+
if (!filename) {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
const baseName = path.basename(filename);
|
|
55
|
+
// Check file prefixes
|
|
56
|
+
for (const prefix of CLAVIX_PATTERNS.filePrefixes) {
|
|
57
|
+
if (baseName.startsWith(prefix)) {
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
// Check if in a clavix-specific directory
|
|
62
|
+
const dirPath = path.dirname(filename);
|
|
63
|
+
for (const pattern of CLAVIX_PATTERNS.directories) {
|
|
64
|
+
if (dirPath.includes(pattern.replace('~', os.homedir()))) {
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Check if a directory path is a known Clavix directory
|
|
72
|
+
* @param dirpath - The directory path to check
|
|
73
|
+
* @returns true if the directory is a Clavix-specific directory
|
|
74
|
+
*/
|
|
75
|
+
export function isClavixDirectory(dirpath) {
|
|
76
|
+
if (!dirpath) {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
const normalizedPath = path.normalize(dirpath);
|
|
80
|
+
for (const pattern of CLAVIX_PATTERNS.directories) {
|
|
81
|
+
const expandedPattern = pattern.replace('~', os.homedir());
|
|
82
|
+
const normalizedPattern = path.normalize(expandedPattern);
|
|
83
|
+
// Check if path is or ends with the pattern
|
|
84
|
+
if (normalizedPath === normalizedPattern ||
|
|
85
|
+
normalizedPath.endsWith(path.sep + normalizedPattern) ||
|
|
86
|
+
normalizedPath.endsWith(path.normalize(path.sep + pattern))) {
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
// Check for wildcard matches (clavix-*)
|
|
90
|
+
if (pattern.includes('clavix-*')) {
|
|
91
|
+
const baseDir = path.dirname(expandedPattern);
|
|
92
|
+
const dirName = path.basename(normalizedPath);
|
|
93
|
+
if (normalizedPath.startsWith(baseDir) && dirName.startsWith('clavix-')) {
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
// Check .clavix directory directly
|
|
99
|
+
const pathParts = normalizedPath.split(path.sep);
|
|
100
|
+
return pathParts.includes('.clavix');
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Check if a file is known to potentially contain Clavix documentation blocks
|
|
104
|
+
* @param filepath - The file path to check
|
|
105
|
+
* @returns true if the file may contain Clavix blocks
|
|
106
|
+
*/
|
|
107
|
+
export function isBlockFile(filepath) {
|
|
108
|
+
const baseName = path.basename(filepath);
|
|
109
|
+
return CLAVIX_PATTERNS.blockFiles.includes(baseName);
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Check if a path should NEVER be deleted (forbidden patterns)
|
|
113
|
+
* @param filepath - The file or directory path to check
|
|
114
|
+
* @returns true if the path should be preserved
|
|
115
|
+
*/
|
|
116
|
+
export function isForbiddenPath(filepath) {
|
|
117
|
+
const normalizedPath = path.normalize(filepath);
|
|
118
|
+
const baseName = path.basename(normalizedPath);
|
|
119
|
+
// Check exact filename matches
|
|
120
|
+
if (CLAVIX_PATTERNS.forbidden.includes(baseName)) {
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
// Check if path contains forbidden directories
|
|
124
|
+
for (const forbidden of CLAVIX_PATTERNS.forbidden) {
|
|
125
|
+
if (forbidden.endsWith('/') &&
|
|
126
|
+
(normalizedPath.includes(forbidden) || normalizedPath.includes(path.normalize(forbidden)))) {
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Comprehensive safety check - determines if a file can be deleted
|
|
134
|
+
* @param filepath - The file path to check
|
|
135
|
+
* @returns true if the file is safe to delete
|
|
136
|
+
*/
|
|
137
|
+
export function shouldDeleteFile(filepath) {
|
|
138
|
+
const normalizedPath = path.normalize(filepath);
|
|
139
|
+
// Check against forbidden paths first
|
|
140
|
+
if (isForbiddenPath(normalizedPath)) {
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
// If it's a Clavix file, it's safe
|
|
144
|
+
if (isClavixFile(normalizedPath)) {
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
// If it's within the target root and is a Clavix directory, it's safe
|
|
148
|
+
if (isClavixDirectory(normalizedPath)) {
|
|
149
|
+
return true;
|
|
150
|
+
}
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Expand a path pattern containing ~ to the actual home directory
|
|
155
|
+
* @param pattern - The path pattern to expand
|
|
156
|
+
* @returns The expanded path
|
|
157
|
+
*/
|
|
158
|
+
export function expandHomePath(pattern) {
|
|
159
|
+
return pattern.replace(/^~(?=$|\/|\\)/, os.homedir());
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Get the list of known Clavix directory patterns
|
|
163
|
+
* @returns Array of Clavix directory patterns
|
|
164
|
+
*/
|
|
165
|
+
export function getClavixDirectoryPatterns() {
|
|
166
|
+
return CLAVIX_PATTERNS.directories;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Get the list of files that may contain Clavix documentation blocks
|
|
170
|
+
* @returns Array of known block file names
|
|
171
|
+
*/
|
|
172
|
+
export function getKnownBlockFiles() {
|
|
173
|
+
return CLAVIX_PATTERNS.blockFiles;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Get the list of file prefixes that indicate Clavix-generated files
|
|
177
|
+
* @returns Array of Clavix file prefixes
|
|
178
|
+
*/
|
|
179
|
+
export function getClavixFilePrefixes() {
|
|
180
|
+
return CLAVIX_PATTERNS.filePrefixes;
|
|
181
|
+
}
|
|
182
|
+
//# sourceMappingURL=guards.js.map
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interactive prompts for the clean command
|
|
3
|
+
*/
|
|
4
|
+
import type { BatchInfo } from './types.js';
|
|
5
|
+
import { CleanScope, ScanMethod } from './types.js';
|
|
6
|
+
/**
|
|
7
|
+
* Result of a batch confirmation action
|
|
8
|
+
*/
|
|
9
|
+
export type BatchAction = 'delete' | 'details' | 'skip' | 'cancel';
|
|
10
|
+
/**
|
|
11
|
+
* Prompts class for interactive user input
|
|
12
|
+
*/
|
|
13
|
+
export declare class Prompts {
|
|
14
|
+
/**
|
|
15
|
+
* Ask user what scope to clean
|
|
16
|
+
* @returns The selected clean scope
|
|
17
|
+
*/
|
|
18
|
+
static selectScope(): Promise<CleanScope>;
|
|
19
|
+
/**
|
|
20
|
+
* Ask user how to discover projects with Clavix artifacts
|
|
21
|
+
* @returns The selected scan method
|
|
22
|
+
*/
|
|
23
|
+
static selectScanMethod(): Promise<ScanMethod>;
|
|
24
|
+
/**
|
|
25
|
+
* Ask user to confirm deletion of a batch
|
|
26
|
+
* @param batch - The batch information to confirm
|
|
27
|
+
* @param dryRun - Whether this is a dry run
|
|
28
|
+
* @returns true if the user wants to delete, false to skip
|
|
29
|
+
*/
|
|
30
|
+
static confirmBatch(batch: BatchInfo, dryRun?: boolean): Promise<boolean>;
|
|
31
|
+
/**
|
|
32
|
+
* Generate the confirmation message for a batch
|
|
33
|
+
*/
|
|
34
|
+
private static getBatchMessage;
|
|
35
|
+
/**
|
|
36
|
+
* Get choices for batch confirmation
|
|
37
|
+
*/
|
|
38
|
+
private static getBatchChoices;
|
|
39
|
+
/**
|
|
40
|
+
* Show detailed information about a batch
|
|
41
|
+
*/
|
|
42
|
+
private static showBatchDetails;
|
|
43
|
+
/**
|
|
44
|
+
* Ask user to confirm scanning all directories (warning)
|
|
45
|
+
* @returns true if user wants to proceed
|
|
46
|
+
*/
|
|
47
|
+
static confirmScanWarning(): Promise<boolean>;
|
|
48
|
+
/**
|
|
49
|
+
* Get custom paths from user
|
|
50
|
+
* @returns Array of paths to scan
|
|
51
|
+
*/
|
|
52
|
+
static getCustomPaths(): Promise<string[]>;
|
|
53
|
+
/**
|
|
54
|
+
* Notify user that no artifacts were found
|
|
55
|
+
* @param scope - The scope that was checked
|
|
56
|
+
*/
|
|
57
|
+
static notifyNoArtifacts(_scope: CleanScope): void;
|
|
58
|
+
/**
|
|
59
|
+
* Format size in human-readable format
|
|
60
|
+
*/
|
|
61
|
+
private static formatSize;
|
|
62
|
+
/**
|
|
63
|
+
* Calculate total size of a batch
|
|
64
|
+
*/
|
|
65
|
+
private static calculateBatchSize;
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=prompts.d.ts.map
|