@@ -2393,6 +2393,33 @@ ${OPENSPEC_MARKERS.end}
23932393 ) ) . toBe ( false ) ;
23942394 } ) ;
23952395
2396+ it . each ( [
2397+ [ 'opsx-archive.md' , 'openspec-archive-change' ] ,
2398+ [ 'opsx-bulk-archive.md' , 'openspec-bulk-archive-change' ] ,
2399+ ] ) ( 'should include sync when replacing legacy Codex %s' , async ( promptName , archiveSkill ) => {
2400+ setMockConfig ( {
2401+ featureFlags : { } ,
2402+ profile : 'core' ,
2403+ delivery : 'skills' ,
2404+ } ) ;
2405+
2406+ const promptDir = path . join ( process . env . CODEX_HOME ! , 'prompts' ) ;
2407+ const managedPrompt = path . join ( promptDir , promptName ) ;
2408+ await fs . mkdir ( promptDir , { recursive : true } ) ;
2409+ await fs . writeFile ( managedPrompt , 'legacy archive prompt' ) ;
2410+
2411+ const forceUpdateCommand = new UpdateCommand ( { force : true } ) ;
2412+ await forceUpdateCommand . execute ( testDir ) ;
2413+
2414+ expect ( await FileSystemUtils . fileExists ( managedPrompt ) ) . toBe ( false ) ;
2415+ expect ( await FileSystemUtils . fileExists (
2416+ path . join ( testDir , '.agents' , 'skills' , archiveSkill , 'SKILL.md' )
2417+ ) ) . toBe ( true ) ;
2418+ expect ( await FileSystemUtils . fileExists (
2419+ path . join ( testDir , '.agents' , 'skills' , 'openspec-sync-specs' , 'SKILL.md' )
2420+ ) ) . toBe ( true ) ;
2421+ } ) ;
2422+
23962423 it ( 'should print a skill-based getting-started menu when a legacy upgrade newly configures codex' , async ( ) => {
23972424 setMockConfig ( {
23982425 featureFlags : { } ,
@@ -3065,40 +3092,63 @@ More user content after markers.
30653092 ) ) . toBe ( false ) ;
30663093 } ) ;
30673094
3068- it ( 'should list missing core workflows when custom profile preserves the old core workflow set' , async ( ) => {
3069- setMockConfig ( {
3070- featureFlags : { } ,
3071- profile : 'custom' ,
3072- delivery : 'both' ,
3073- workflows : [ 'propose' , 'explore' , 'apply' , 'archive' ] ,
3074- } ) ;
3075-
3076- const initCommand = new InitCommand ( { tools : 'claude' , force : true } ) ;
3077- await initCommand . execute ( testDir ) ;
3078-
3079- const consoleSpy = vi . spyOn ( console , 'log' ) ;
3095+ it . each ( [ 'skills' , 'commands' , 'both' ] as const ) (
3096+ 'should repair an archive profile missing sync with %s delivery' ,
3097+ async ( delivery ) => {
3098+ setMockConfig ( {
3099+ featureFlags : { } ,
3100+ profile : 'custom' ,
3101+ delivery,
3102+ workflows : [ 'propose' , 'explore' , 'apply' , 'archive' ] ,
3103+ } ) ;
30803104
3081- await updateCommand . execute ( testDir ) ;
3105+ const archiveSkill = path . join (
3106+ testDir ,
3107+ '.claude' ,
3108+ 'skills' ,
3109+ 'openspec-archive-change' ,
3110+ 'SKILL.md'
3111+ ) ;
3112+ const archiveCommand = path . join (
3113+ testDir ,
3114+ '.claude' ,
3115+ 'commands' ,
3116+ 'opsx' ,
3117+ 'archive.md'
3118+ ) ;
3119+ if ( delivery !== 'commands' ) {
3120+ await fs . mkdir ( path . dirname ( archiveSkill ) , { recursive : true } ) ;
3121+ await fs . writeFile ( archiveSkill , 'old archive skill' ) ;
3122+ }
3123+ if ( delivery !== 'skills' ) {
3124+ await fs . mkdir ( path . dirname ( archiveCommand ) , { recursive : true } ) ;
3125+ await fs . writeFile ( archiveCommand , 'old archive command' ) ;
3126+ }
30823127
3083- const calls = consoleSpy . mock . calls . map ( call =>
3084- call . map ( arg => String ( arg ) ) . join ( ' ' )
3085- ) ;
3086- expect ( calls . some ( call =>
3087- call . includes ( 'Your custom profile is missing 2 core workflows: update, sync' )
3088- ) ) . toBe ( true ) ;
3089- expect ( calls . some ( call =>
3090- call . includes ( 'openspec config profile core' )
3091- ) ) . toBe ( true ) ;
3128+ const consoleSpy = vi . spyOn ( console , 'log' ) ;
30923129
3093- expect ( await FileSystemUtils . fileExists (
3094- path . join ( testDir , '.claude' , 'skills' , 'openspec-sync-specs' , 'SKILL.md' )
3095- ) ) . toBe ( false ) ;
3096- expect ( await FileSystemUtils . fileExists (
3097- path . join ( testDir , '.claude' , 'commands' , 'opsx' , 'sync.md' )
3098- ) ) . toBe ( false ) ;
3130+ await updateCommand . execute ( testDir ) ;
30993131
3100- consoleSpy . mockRestore ( ) ;
3101- } ) ;
3132+ const calls = consoleSpy . mock . calls . map ( call =>
3133+ call . map ( arg => String ( arg ) ) . join ( ' ' )
3134+ ) ;
3135+ expect ( calls . some ( call =>
3136+ call . includes ( 'Your custom profile is missing 1 core workflow: update' )
3137+ ) ) . toBe ( true ) ;
3138+ expect ( calls . some ( call =>
3139+ call . includes ( 'openspec config profile core' )
3140+ ) ) . toBe ( true ) ;
3141+
3142+ expect ( await FileSystemUtils . fileExists (
3143+ path . join ( testDir , '.claude' , 'skills' , 'openspec-sync-specs' , 'SKILL.md' )
3144+ ) ) . toBe ( delivery !== 'commands' ) ;
3145+ expect ( await FileSystemUtils . fileExists (
3146+ path . join ( testDir , '.claude' , 'commands' , 'opsx' , 'sync.md' )
3147+ ) ) . toBe ( delivery !== 'skills' ) ;
3148+
3149+ consoleSpy . mockRestore ( ) ;
3150+ }
3151+ ) ;
31023152
31033153 it ( 'should list a single missing core workflow when custom profile lacks only update' , async ( ) => {
31043154 setMockConfig ( {
0 commit comments