@@ -12,7 +12,8 @@ import { ClavixConfig, DEFAULT_CONFIG } from '../../types/config';
1212import { CommandTemplate , AgentAdapter } from '../../types/agent' ;
1313import { GeminiAdapter } from '../../core/adapters/gemini-adapter' ;
1414import { QwenAdapter } from '../../core/adapters/qwen-adapter' ;
15- import { parseTomlSlashCommand } from '../../utils/toml-templates' ;
15+ import { loadCommandTemplates } from '../../utils/template-loader' ;
16+ import { collectLegacyCommandFiles } from '../../utils/legacy-command-cleanup' ;
1617
1718export default class Init extends Command {
1819 static description = 'Initialize Clavix in the current project' ;
@@ -60,7 +61,7 @@ export default class Init extends Command {
6061 value : 'claude-code' ,
6162 } ,
6263 {
63- name : 'Cline (.cline /workflows/)' ,
64+ name : 'Cline (.clinerules /workflows/)' ,
6465 value : 'cline' ,
6566 } ,
6667 {
@@ -247,21 +248,24 @@ export default class Init extends Command {
247248 }
248249 }
249250
250- // Migrate from old command structure if needed (Claude Code only)
251- if ( providerName === 'claude-code' ) {
252- await this . migrateOldCommands ( adapter ) ;
253- }
254-
255251 // Generate slash commands
256252 const generatedTemplates = await this . generateSlashCommands ( adapter ) ;
257253
254+ await this . handleLegacyCommands ( adapter , generatedTemplates ) ;
255+
258256 if ( adapter . name === 'gemini' || adapter . name === 'qwen' ) {
259257 const commandPath = adapter . getCommandPath ( ) ;
260258 const isNamespaced = commandPath . endsWith ( path . join ( 'commands' , 'clavix' ) ) ;
261259 const namespace = isNamespaced ? path . basename ( commandPath ) : undefined ;
262- const commandNames = generatedTemplates . map ( ( template ) =>
263- isNamespaced ? `/${ namespace } :${ template . name } ` : `/${ template . name } `
264- ) ;
260+ const commandNames = generatedTemplates . map ( ( template ) => {
261+ if ( isNamespaced ) {
262+ return `/${ namespace } :${ template . name } ` ;
263+ }
264+
265+ const filename = adapter . getTargetFilename ( template . name ) ;
266+ const slashName = filename . slice ( 0 , - adapter . fileExtension . length ) ;
267+ return `/${ slashName } ` ;
268+ } ) ;
265269
266270 console . log ( chalk . green ( ` → Registered ${ commandNames . join ( ', ' ) } ` ) ) ;
267271 console . log ( chalk . gray ( ` Commands saved to ${ commandPath } ` ) ) ;
@@ -371,97 +375,59 @@ See documentation for template format details.
371375 }
372376
373377 private async generateSlashCommands ( adapter : AgentAdapter ) : Promise < CommandTemplate [ ] > {
374- const templateDir = path . join ( __dirname , '../../templates/slash-commands' , adapter . name ) ;
375- const files = await FileSystem . listFiles ( templateDir ) ;
376- const extension = adapter . fileExtension ;
377- const commandFiles = files . filter ( ( file ) => file . endsWith ( extension ) ) ;
378-
379- const templates : CommandTemplate [ ] = [ ] ;
380-
381- for ( const file of commandFiles ) {
382- const content = await FileSystem . readFile ( path . join ( templateDir , file ) ) ;
383- const name = file . slice ( 0 , - extension . length ) ;
384-
385- if ( extension === '.toml' ) {
386- const parsed = parseTomlSlashCommand ( content , name , adapter . name ) ;
387- templates . push ( {
388- name,
389- content : parsed . prompt ,
390- description : parsed . description ,
391- } ) ;
392- } else {
393- templates . push ( {
394- name,
395- content,
396- description : this . extractDescription ( content ) ,
397- } ) ;
398- }
399- }
378+ const templates = await loadCommandTemplates ( adapter ) ;
400379
401380 await adapter . generateCommands ( templates ) ;
402381 return templates ;
403382 }
404383
405- private async injectDocumentation ( adapter : AgentAdapter ) : Promise < void > {
406- // Inject AGENTS.md
407- const agentsContent = DocInjector . getDefaultAgentsContent ( ) ;
408- await DocInjector . injectBlock ( 'AGENTS.md' , this . extractClavixBlock ( agentsContent ) ) ;
409-
410- // Inject CLAUDE.md if Claude Code selected
411- if ( adapter . name === 'claude-code' ) {
412- const claudeContent = DocInjector . getDefaultClaudeContent ( ) ;
413- await DocInjector . injectBlock ( 'CLAUDE.md' , this . extractClavixBlock ( claudeContent ) ) ;
414- }
415- }
416-
417- private async migrateOldCommands ( _adapter : AgentAdapter ) : Promise < void > {
418- // Check for old command structure (.claude/commands/clavix:*.md)
419- const oldCommandsPath = '.claude/commands' ;
384+ private async handleLegacyCommands ( adapter : AgentAdapter , templates : CommandTemplate [ ] ) : Promise < void > {
385+ const commandNames = templates . map ( ( template ) => template . name ) ;
386+ const legacyFiles = await collectLegacyCommandFiles ( adapter , commandNames ) ;
420387
421- if ( ! await FileSystem . exists ( oldCommandsPath ) ) {
388+ if ( legacyFiles . length === 0 ) {
422389 return ;
423390 }
424391
425- try {
426- const files = await FileSystem . listFiles ( oldCommandsPath , / ^ c l a v i x : .* \. m d $ / ) ;
392+ const relativePaths = legacyFiles
393+ . map ( ( file ) => path . relative ( process . cwd ( ) , file ) )
394+ . sort ( ( a , b ) => a . localeCompare ( b ) ) ;
427395
428- if ( files . length === 0 ) {
429- return ;
430- }
431-
432- console . log ( chalk . cyan ( '🔄 Migrating old command structure...' ) ) ;
396+ console . log ( chalk . gray ( ` ⚠ Found ${ relativePaths . length } deprecated command file(s):` ) ) ;
397+ for ( const file of relativePaths ) {
398+ console . log ( chalk . gray ( ` • ${ file } ` ) ) ;
399+ }
433400
434- let removed = 0 ;
435- for ( const file of files ) {
436- const filePath = path . join ( oldCommandsPath , file ) ;
437- if ( await FileSystem . exists ( filePath ) ) {
438- await FileSystem . remove ( filePath ) ;
439- console . log ( chalk . gray ( ` ✓ Removed old command: ${ file } ` ) ) ;
440- removed ++ ;
441- }
442- }
401+ const { removeLegacy } = await inquirer . prompt ( [
402+ {
403+ type : 'confirm' ,
404+ name : 'removeLegacy' ,
405+ message : `Remove deprecated files for ${ adapter . displayName } ? Functionality is unchanged; filenames are being standardized.` ,
406+ default : true ,
407+ } ,
408+ ] ) ;
409+
410+ if ( ! removeLegacy ) {
411+ console . log ( chalk . gray ( ' ⊗ Kept legacy files (deprecated naming retained)' ) ) ;
412+ return ;
413+ }
443414
444- if ( removed > 0 ) {
445- console . log ( chalk . green ( ` ✓ Migration complete: removed ${ removed } old command file(s)` ) ) ;
446- }
447- } catch {
448- // Non-fatal error - log but continue
449- console . log ( chalk . yellow ( ' ⚠ Could not migrate old commands (non-fatal)' ) ) ;
415+ for ( const file of legacyFiles ) {
416+ await FileSystem . remove ( file ) ;
417+ console . log ( chalk . gray ( ` ✓ Removed ${ path . relative ( process . cwd ( ) , file ) } ` ) ) ;
450418 }
451419 }
452420
453- private extractDescription ( content : string ) : string {
454- const yamlMatch = content . match ( / d e s c r i p t i o n : \s * ( .+ ) / ) ;
455- if ( yamlMatch ) {
456- return yamlMatch [ 1 ] . trim ( ) . replace ( / ^ [ ' " ] | [ ' " ] $ / g, '' ) ;
457- }
421+ private async injectDocumentation ( adapter : AgentAdapter ) : Promise < void > {
422+ // Inject AGENTS.md
423+ const agentsContent = DocInjector . getDefaultAgentsContent ( ) ;
424+ await DocInjector . injectBlock ( 'AGENTS.md' , this . extractClavixBlock ( agentsContent ) ) ;
458425
459- const tomlMatch = content . match ( / d e s c r i p t i o n \s * = \s * [ ' " ] ? ( .+ ?) [ ' " ] ? (?: \r ? \n | $ ) / ) ;
460- if ( tomlMatch ) {
461- return tomlMatch [ 1 ] . trim ( ) . replace ( / ^ [ ' " ] | [ ' " ] $ / g, '' ) ;
426+ // Inject CLAUDE.md if Claude Code selected
427+ if ( adapter . name === 'claude-code' ) {
428+ const claudeContent = DocInjector . getDefaultClaudeContent ( ) ;
429+ await DocInjector . injectBlock ( 'CLAUDE.md' , this . extractClavixBlock ( claudeContent ) ) ;
462430 }
463-
464- return '' ;
465431 }
466432
467433
0 commit comments