@@ -833,6 +833,73 @@ interface ArtifactExperimentalSetupOptions {
833833 selectedTools ?: string [ ] ; // For multi-select from interactive prompt
834834}
835835
836+ /**
837+ * Names of experimental skill directories created by openspec experimental.
838+ */
839+ const EXPERIMENTAL_SKILL_NAMES = [
840+ 'openspec-explore' ,
841+ 'openspec-new-change' ,
842+ 'openspec-continue-change' ,
843+ 'openspec-apply-change' ,
844+ 'openspec-ff-change' ,
845+ 'openspec-sync-specs' ,
846+ 'openspec-archive-change' ,
847+ 'openspec-bulk-archive-change' ,
848+ 'openspec-verify-change' ,
849+ ] ;
850+
851+ /**
852+ * Status of experimental skill configuration for a tool.
853+ */
854+ interface ToolExperimentalStatus {
855+ /** Whether the tool has any experimental skills configured */
856+ configured : boolean ;
857+ /** Whether all 9 experimental skills are configured */
858+ fullyConfigured : boolean ;
859+ /** Number of skills currently configured (0-9) */
860+ skillCount : number ;
861+ }
862+
863+ /**
864+ * Checks which experimental skill files exist for a tool.
865+ */
866+ function getToolExperimentalStatus ( projectRoot : string , toolId : string ) : ToolExperimentalStatus {
867+ const tool = AI_TOOLS . find ( ( t ) => t . value === toolId ) ;
868+ if ( ! tool ?. skillsDir ) {
869+ return { configured : false , fullyConfigured : false , skillCount : 0 } ;
870+ }
871+
872+ const skillsDir = path . join ( projectRoot , tool . skillsDir , 'skills' ) ;
873+ let skillCount = 0 ;
874+
875+ for ( const skillName of EXPERIMENTAL_SKILL_NAMES ) {
876+ const skillFile = path . join ( skillsDir , skillName , 'SKILL.md' ) ;
877+ if ( fs . existsSync ( skillFile ) ) {
878+ skillCount ++ ;
879+ }
880+ }
881+
882+ return {
883+ configured : skillCount > 0 ,
884+ fullyConfigured : skillCount === EXPERIMENTAL_SKILL_NAMES . length ,
885+ skillCount,
886+ } ;
887+ }
888+
889+ /**
890+ * Gets the experimental status for all tools with skillsDir configured.
891+ */
892+ function getExperimentalToolStates ( projectRoot : string ) : Map < string , ToolExperimentalStatus > {
893+ const states = new Map < string , ToolExperimentalStatus > ( ) ;
894+ const toolIds = AI_TOOLS . filter ( ( t ) => t . skillsDir ) . map ( ( t ) => t . value ) ;
895+
896+ for ( const toolId of toolIds ) {
897+ states . set ( toolId , getToolExperimentalStatus ( projectRoot , toolId ) ) ;
898+ }
899+
900+ return states ;
901+ }
902+
836903/**
837904 * Gets the list of tools with skillsDir configured.
838905 */
@@ -860,13 +927,34 @@ async function artifactExperimentalSetupCommand(options: ArtifactExperimentalSet
860927
861928 const { searchableMultiSelect } = await import ( '../prompts/searchable-multi-select.js' ) ;
862929
930+ // Get experimental status for all tools to show configured indicators
931+ const toolStates = getExperimentalToolStates ( projectRoot ) ;
932+
933+ // Build choices with configured status and sort configured tools first
934+ const sortedChoices = validTools
935+ . map ( ( toolId ) => {
936+ const tool = AI_TOOLS . find ( ( t ) => t . value === toolId ) ;
937+ const status = toolStates . get ( toolId ) ;
938+ const configured = status ?. configured ?? false ;
939+
940+ return {
941+ name : tool ?. name || toolId ,
942+ value : toolId ,
943+ configured,
944+ preSelected : configured , // Pre-select configured tools for easy refresh
945+ } ;
946+ } )
947+ . sort ( ( a , b ) => {
948+ // Configured tools first
949+ if ( a . configured && ! b . configured ) return - 1 ;
950+ if ( ! a . configured && b . configured ) return 1 ;
951+ return 0 ;
952+ } ) ;
953+
863954 const selectedTools = await searchableMultiSelect ( {
864955 message : `Select tools to set up (${ validTools . length } available)` ,
865956 pageSize : 15 ,
866- choices : validTools . map ( ( toolId ) => {
867- const tool = AI_TOOLS . find ( ( t ) => t . value === toolId ) ;
868- return { name : tool ?. name || toolId , value : toolId } ;
869- } ) ,
957+ choices : sortedChoices ,
870958 validate : ( selected : string [ ] ) => selected . length > 0 || 'Select at least one tool' ,
871959 } ) ;
872960
@@ -886,8 +974,11 @@ async function artifactExperimentalSetupCommand(options: ArtifactExperimentalSet
886974 // Determine tools to set up
887975 const toolsToSetup = options . selectedTools || [ options . tool ! ] ;
888976
977+ // Get tool states before processing to track created vs refreshed
978+ const preSetupStates = getExperimentalToolStates ( projectRoot ) ;
979+
889980 // Validate all tools before starting
890- const validatedTools : Array < { value : string ; name : string ; skillsDir : string } > = [ ] ;
981+ const validatedTools : Array < { value : string ; name : string ; skillsDir : string ; wasConfigured : boolean } > = [ ] ;
891982 for ( const toolId of toolsToSetup ) {
892983 const tool = AI_TOOLS . find ( ( t ) => t . value === toolId ) ;
893984 if ( ! tool ) {
@@ -904,7 +995,13 @@ async function artifactExperimentalSetupCommand(options: ArtifactExperimentalSet
904995 ) ;
905996 }
906997
907- validatedTools . push ( { value : tool . value , name : tool . name , skillsDir : tool . skillsDir } ) ;
998+ const preState = preSetupStates . get ( tool . value ) ;
999+ validatedTools . push ( {
1000+ value : tool . value ,
1001+ name : tool . name ,
1002+ skillsDir : tool . skillsDir ,
1003+ wasConfigured : preState ?. configured ?? false ,
1004+ } ) ;
9081005 }
9091006
9101007 // Track all created files across all tools
@@ -1016,139 +1113,74 @@ ${template.instructions}
10161113 // Filter to only successfully configured tools
10171114 const successfulTools = validatedTools . filter ( t => ! failedTools . some ( f => f . name === t . name ) ) ;
10181115
1019- // Print success message
1116+ // Print success summary
10201117 console . log ( ) ;
1021- console . log ( chalk . bold ( `🧪 Experimental Artifact Workflow Setup Complete` ) ) ;
1118+ console . log ( chalk . bold ( ' Experimental Artifact Workflow Setup Complete' ) ) ;
10221119 console . log ( ) ;
1120+
1121+ // Tools and counts (show unique counts, not total files across all tools)
10231122 if ( successfulTools . length > 0 ) {
1024- console . log ( chalk . bold ( `Tools configured: ${ successfulTools . map ( t => t . name ) . join ( ', ' ) } ` ) ) ;
1025- }
1026- if ( failedTools . length > 0 ) {
1027- console . log ( chalk . red ( `Tools failed: ${ failedTools . map ( f => f . name ) . join ( ', ' ) } ` ) ) ;
1028- }
1029- console . log ( ) ;
1123+ // Separate newly created tools from refreshed (previously configured) tools
1124+ const createdTools = successfulTools . filter ( t => ! t . wasConfigured ) ;
1125+ const refreshedTools = successfulTools . filter ( t => t . wasConfigured ) ;
10301126
1031- console . log ( chalk . bold ( 'Skills Created:' ) ) ;
1032- for ( const file of allCreatedSkillFiles ) {
1033- console . log ( chalk . green ( ' ✓ ' + file ) ) ;
1034- }
1035- console . log ( ) ;
1127+ if ( createdTools . length > 0 ) {
1128+ console . log ( `Created: ${ createdTools . map ( t => t . name ) . join ( ', ' ) } ` ) ;
1129+ }
1130+ if ( refreshedTools . length > 0 ) {
1131+ console . log ( `Refreshed: ${ refreshedTools . map ( t => t . name ) . join ( ', ' ) } ` ) ;
1132+ }
10361133
1037- if ( anyCommandsSkipped ) {
1038- console . log ( chalk . yellow ( `Command generation skipped for: ${ toolsWithSkippedCommands . join ( ', ' ) } (no adapter)` ) ) ;
1039- console . log ( ) ;
1134+ const uniqueSkillCount = skillTemplates . length ;
1135+ const uniqueCommandCount = commandContents . length ;
1136+ const toolDirs = [ ...new Set ( successfulTools . map ( t => t . skillsDir ) ) ] . join ( ', ' ) ;
1137+ // Only count commands if any were actually created (some tools may not have adapters)
1138+ const hasCommands = allCreatedCommandFiles . length > 0 ;
1139+ if ( hasCommands ) {
1140+ console . log ( `${ uniqueSkillCount } skills and ${ uniqueCommandCount } commands in ${ toolDirs } /` ) ;
1141+ } else {
1142+ console . log ( `${ uniqueSkillCount } skills in ${ toolDirs } /` ) ;
1143+ }
10401144 }
10411145
1042- if ( allCreatedCommandFiles . length > 0 ) {
1043- console . log ( chalk . bold ( 'Slash Commands Created:' ) ) ;
1044- for ( const file of allCreatedCommandFiles ) {
1045- console . log ( chalk . green ( ' ✓ ' + file ) ) ;
1046- }
1047- console . log ( ) ;
1146+ if ( failedTools . length > 0 ) {
1147+ console . log ( chalk . red ( `Failed: ${ failedTools . map ( f => `${ f . name } (${ f . error . message } )` ) . join ( ', ' ) } ` ) ) ;
10481148 }
10491149
1050- // Config creation section (happens once, not per-tool)
1051- console . log ( '━' . repeat ( 70 ) ) ;
1052- console . log ( ) ;
1053- console . log ( chalk . bold ( '📋 Project Configuration (Optional)' ) ) ;
1054- console . log ( ) ;
1055- console . log ( 'Configure project defaults for OpenSpec workflows.' ) ;
1056- console . log ( ) ;
1150+ if ( anyCommandsSkipped ) {
1151+ console . log ( chalk . dim ( `Commands skipped for: ${ toolsWithSkippedCommands . join ( ', ' ) } (no adapter)` ) ) ;
1152+ }
10571153
1058- // Check if config already exists
1154+ // Config creation (simplified)
10591155 const configPath = path . join ( projectRoot , 'openspec' , 'config.yaml' ) ;
10601156 const configYmlPath = path . join ( projectRoot , 'openspec' , 'config.yml' ) ;
10611157 const configExists = fs . existsSync ( configPath ) || fs . existsSync ( configYmlPath ) ;
10621158
10631159 if ( configExists ) {
1064- // Config already exists, skip creation
1065- console . log ( chalk . blue ( 'ℹ️ openspec/config.yaml already exists. Skipping config creation.' ) ) ;
1066- console . log ( ) ;
1067- console . log ( ' To update config, edit openspec/config.yaml manually or:' ) ;
1068- console . log ( ' 1. Delete openspec/config.yaml' ) ;
1069- console . log ( ' 2. Run openspec artifact-experimental-setup again' ) ;
1070- console . log ( ) ;
1160+ console . log ( `Config: openspec/config.yaml (exists)` ) ;
10711161 } else if ( ! isInteractive ( options ) ) {
1072- // Non-interactive mode (CI, automation, piped input, or --no-interactive flag)
1073- console . log ( chalk . blue ( 'ℹ️ Skipping config prompts (non-interactive mode)' ) ) ;
1074- console . log ( ) ;
1075- console . log ( ' To create config manually, add openspec/config.yaml with:' ) ;
1076- console . log ( chalk . dim ( ' schema: spec-driven' ) ) ;
1077- console . log ( ) ;
1162+ console . log ( chalk . dim ( `Config: skipped (non-interactive mode)` ) ) ;
10781163 } else {
1079- // Create config with default schema
10801164 const yamlContent = serializeConfig ( { schema : DEFAULT_SCHEMA } ) ;
1081-
10821165 try {
10831166 await FileSystemUtils . writeFile ( configPath , yamlContent ) ;
1084-
1085- console . log ( ) ;
1086- console . log ( chalk . green ( '✓ Created openspec/config.yaml' ) ) ;
1087- console . log ( ) ;
1088- console . log ( ` Default schema: ${ chalk . cyan ( DEFAULT_SCHEMA ) } ` ) ;
1089- console . log ( ) ;
1090- console . log ( chalk . dim ( ' Edit the file to add project context and per-artifact rules.' ) ) ;
1091- console . log ( ) ;
1092-
1093- // Git commit suggestion with all tool directories
1094- const toolDirs = validatedTools . map ( t => t . skillsDir + '/' ) . join ( ' ' ) ;
1095- console . log ( chalk . bold ( 'To share with team:' ) ) ;
1096- console . log ( chalk . dim ( ` git add openspec/config.yaml ${ toolDirs } ` ) ) ;
1097- console . log ( chalk . dim ( ' git commit -m "Setup OpenSpec experimental workflow"' ) ) ;
1098- console . log ( ) ;
1167+ console . log ( `Config: openspec/config.yaml (schema: ${ DEFAULT_SCHEMA } )` ) ;
10991168 } catch ( writeError ) {
1100- // Handle file write errors
1101- console . error ( ) ;
1102- console . error ( chalk . red ( '✗ Failed to write openspec/config.yaml' ) ) ;
1103- console . error ( chalk . dim ( ` ${ ( writeError as Error ) . message } ` ) ) ;
1104- console . error ( ) ;
1105- console . error ( 'Fallback: Create config manually:' ) ;
1106- console . error ( chalk . dim ( ' 1. Create openspec/config.yaml' ) ) ;
1107- console . error ( chalk . dim ( ' 2. Copy the following content:' ) ) ;
1108- console . error ( ) ;
1109- console . error ( chalk . dim ( yamlContent ) ) ;
1110- console . error ( ) ;
1169+ console . log ( chalk . red ( `Config: failed to create (${ ( writeError as Error ) . message } )` ) ) ;
11111170 }
11121171 }
11131172
1114- console . log ( '━' . repeat ( 70 ) ) ;
1115- console . log ( ) ;
1116- console . log ( chalk . bold ( '📖 Usage:' ) ) ;
1117- console . log ( ) ;
1118- console . log ( ' ' + chalk . cyan ( 'Skills' ) + ' work automatically in compatible editors:' ) ;
1119- for ( const tool of validatedTools ) {
1120- console . log ( ` • ${ tool . name } - Skills in ${ tool . skillsDir } /skills/` ) ;
1121- }
1173+ // Getting started
11221174 console . log ( ) ;
1123- console . log ( ' Ask naturally:' ) ;
1124- console . log ( ' • "I want to start a new OpenSpec change to add <feature>"' ) ;
1125- console . log ( ' • "Continue working on this change"' ) ;
1126- console . log ( ' • "Implement the tasks for this change"' ) ;
1127- console . log ( ) ;
1128- if ( allCreatedCommandFiles . length > 0 ) {
1129- console . log ( ' ' + chalk . cyan ( 'Slash Commands' ) + ' for explicit invocation:' ) ;
1130- console . log ( ' • /opsx:explore - Think through ideas, investigate problems' ) ;
1131- console . log ( ' • /opsx:new - Start a new change' ) ;
1132- console . log ( ' • /opsx:continue - Create the next artifact' ) ;
1133- console . log ( ' • /opsx:apply - Implement tasks' ) ;
1134- console . log ( ' • /opsx:ff - Fast-forward: create all artifacts at once' ) ;
1135- console . log ( ' • /opsx:sync - Sync delta specs to main specs' ) ;
1136- console . log ( ' • /opsx:verify - Verify implementation matches artifacts' ) ;
1137- console . log ( ' • /opsx:archive - Archive a completed change' ) ;
1138- console . log ( ' • /opsx:bulk-archive - Archive multiple completed changes' ) ;
1139- console . log ( ) ;
1140- }
1141- // Report any failures at the end
1142- if ( failedTools . length > 0 ) {
1143- console . log ( chalk . red ( '⚠️ Some tools failed to set up:' ) ) ;
1144- for ( const { name, error } of failedTools ) {
1145- console . log ( chalk . red ( ` • ${ name } : ${ error . message } ` ) ) ;
1146- }
1147- console . log ( ) ;
1148- }
1175+ console . log ( chalk . bold ( 'Getting started:' ) ) ;
1176+ console . log ( ' /opsx:new Start a new change' ) ;
1177+ console . log ( ' /opsx:continue Create the next artifact' ) ;
1178+ console . log ( ' /opsx:apply Implement tasks' ) ;
11491179
1150- console . log ( chalk . yellow ( '💡 This is an experimental feature.' ) ) ;
1151- console . log ( ' Feedback welcome at: https://github.com/Fission-AI/OpenSpec/issues' ) ;
1180+ // Links
1181+ console . log ( ) ;
1182+ console . log ( `Learn more: ${ chalk . cyan ( 'https://github.com/Fission-AI/OpenSpec/blob/main/docs/experimental-workflow.md' ) } ` ) ;
1183+ console . log ( `Feedback: ${ chalk . cyan ( 'https://github.com/Fission-AI/OpenSpec/issues' ) } ` ) ;
11521184 console . log ( ) ;
11531185}
11541186
@@ -1285,7 +1317,7 @@ export function registerArtifactWorkflowCommands(program: Command): void {
12851317
12861318 // Artifact experimental setup command
12871319 program
1288- . command ( 'artifact- experimental-setup ' )
1320+ . command ( 'experimental' )
12891321 . description ( '[Experimental] Setup Agent Skills for the experimental artifact workflow' )
12901322 . option ( '--tool <tool-id>' , 'Target AI tool (e.g., claude, cursor, windsurf)' )
12911323 . option ( '--no-interactive' , 'Disable interactive prompts' )
0 commit comments