@@ -95,19 +95,227 @@ async function attemptAutoMerge(cwd: string, promptPath: string, autoExecute: bo
9595 }
9696}
9797
98+ /**
99+ * Re-initialization strategy options
100+ */
101+ type ReinitStrategy = 'upgrade' | 'reset-config' | 'full-reset' | 'cancel' ;
102+
103+ /**
104+ * Handle re-initialization when LeanSpec is already initialized
105+ */
106+ async function handleReinitialize ( cwd : string , skipPrompts : boolean , forceReinit : boolean ) : Promise < ReinitStrategy > {
107+ const specsDir = path . join ( cwd , 'specs' ) ;
108+ let specCount = 0 ;
109+
110+ try {
111+ const entries = await fs . readdir ( specsDir , { withFileTypes : true } ) ;
112+ specCount = entries . filter ( e => e . isDirectory ( ) ) . length ;
113+ } catch {
114+ // specs/ doesn't exist
115+ }
116+
117+ console . log ( '' ) ;
118+ console . log ( chalk . yellow ( '⚠ LeanSpec is already initialized in this directory.' ) ) ;
119+
120+ if ( specCount > 0 ) {
121+ console . log ( chalk . cyan ( ` Found ${ specCount } spec${ specCount > 1 ? 's' : '' } in specs/` ) ) ;
122+ }
123+ console . log ( '' ) ;
124+
125+ // Force flag: reset config but preserve specs (safe default)
126+ if ( forceReinit ) {
127+ console . log ( chalk . gray ( 'Force flag detected. Resetting configuration...' ) ) ;
128+ return 'reset-config' ;
129+ }
130+
131+ // With -y flag, default to upgrade (safest)
132+ if ( skipPrompts ) {
133+ console . log ( chalk . gray ( 'Using safe upgrade (preserving all existing files)' ) ) ;
134+ return 'upgrade' ;
135+ }
136+
137+ // Interactive mode: let user choose
138+ const strategy = await select < ReinitStrategy > ( {
139+ message : 'What would you like to do?' ,
140+ choices : [
141+ {
142+ name : 'Upgrade configuration (recommended)' ,
143+ value : 'upgrade' ,
144+ description : 'Update config to latest version. Keeps specs and AGENTS.md untouched.' ,
145+ } ,
146+ {
147+ name : 'Reset configuration' ,
148+ value : 'reset-config' ,
149+ description : 'Fresh config from template. Keeps specs/ directory.' ,
150+ } ,
151+ {
152+ name : 'Full reset' ,
153+ value : 'full-reset' ,
154+ description : 'Remove .lean-spec/, specs/, and AGENTS.md. Start completely fresh.' ,
155+ } ,
156+ {
157+ name : 'Cancel' ,
158+ value : 'cancel' ,
159+ description : 'Exit without changes.' ,
160+ } ,
161+ ] ,
162+ } ) ;
163+
164+ // Confirm destructive actions
165+ if ( strategy === 'full-reset' ) {
166+ const warnings : string [ ] = [ ] ;
167+
168+ if ( specCount > 0 ) {
169+ warnings . push ( `${ specCount } spec${ specCount > 1 ? 's' : '' } in specs/` ) ;
170+ }
171+
172+ try {
173+ await fs . access ( path . join ( cwd , 'AGENTS.md' ) ) ;
174+ warnings . push ( 'AGENTS.md' ) ;
175+ } catch { }
176+
177+ if ( warnings . length > 0 ) {
178+ console . log ( '' ) ;
179+ console . log ( chalk . red ( '⚠ This will permanently delete:' ) ) ;
180+ for ( const warning of warnings ) {
181+ console . log ( chalk . red ( ` - ${ warning } ` ) ) ;
182+ }
183+ console . log ( '' ) ;
184+
185+ const confirmed = await confirm ( {
186+ message : 'Are you sure you want to continue?' ,
187+ default : false ,
188+ } ) ;
189+
190+ if ( ! confirmed ) {
191+ console . log ( chalk . gray ( 'Cancelled.' ) ) ;
192+ return 'cancel' ;
193+ }
194+ }
195+
196+ // Perform full reset
197+ console . log ( chalk . gray ( 'Performing full reset...' ) ) ;
198+
199+ // Remove .lean-spec/
200+ await fs . rm ( path . join ( cwd , '.lean-spec' ) , { recursive : true , force : true } ) ;
201+ console . log ( chalk . gray ( ' Removed .lean-spec/' ) ) ;
202+
203+ // Remove specs/
204+ try {
205+ await fs . rm ( specsDir , { recursive : true , force : true } ) ;
206+ console . log ( chalk . gray ( ' Removed specs/' ) ) ;
207+ } catch { }
208+
209+ // Remove AGENTS.md and symlinks
210+ for ( const file of [ 'AGENTS.md' , 'CLAUDE.md' , 'GEMINI.md' ] ) {
211+ try {
212+ await fs . rm ( path . join ( cwd , file ) , { force : true } ) ;
213+ console . log ( chalk . gray ( ` Removed ${ file } ` ) ) ;
214+ } catch { }
215+ }
216+ }
217+
218+ return strategy ;
219+ }
220+
221+ /**
222+ * Upgrade existing LeanSpec configuration
223+ * This preserves all user content (specs, AGENTS.md) while updating config
224+ */
225+ async function upgradeConfig ( cwd : string ) : Promise < void > {
226+ const configPath = path . join ( cwd , '.lean-spec' , 'config.json' ) ;
227+
228+ // Read existing config
229+ let existingConfig : LeanSpecConfig ;
230+ try {
231+ const content = await fs . readFile ( configPath , 'utf-8' ) ;
232+ existingConfig = JSON . parse ( content ) ;
233+ } catch {
234+ console . error ( chalk . red ( 'Error reading existing config' ) ) ;
235+ process . exit ( 1 ) ;
236+ }
237+
238+ // Load standard template config as reference for defaults
239+ const templateConfigPath = path . join ( TEMPLATES_DIR , 'standard' , 'config.json' ) ;
240+ let templateConfig : LeanSpecConfig ;
241+ try {
242+ const content = await fs . readFile ( templateConfigPath , 'utf-8' ) ;
243+ templateConfig = JSON . parse ( content ) . config ;
244+ } catch {
245+ console . error ( chalk . red ( 'Error reading template config' ) ) ;
246+ process . exit ( 1 ) ;
247+ }
248+
249+ // Merge configs - preserve user settings, add new defaults
250+ const upgradedConfig : LeanSpecConfig = {
251+ ...templateConfig ,
252+ ...existingConfig ,
253+ // Deep merge structure
254+ structure : {
255+ ...templateConfig . structure ,
256+ ...existingConfig . structure ,
257+ } ,
258+ } ;
259+
260+ // Ensure templates directory exists
261+ const templatesDir = path . join ( cwd , '.lean-spec' , 'templates' ) ;
262+ try {
263+ await fs . mkdir ( templatesDir , { recursive : true } ) ;
264+ } catch { }
265+
266+ // Check if templates need updating
267+ const templateFiles = [ 'spec-template.md' ] ;
268+ let templatesUpdated = false ;
269+
270+ for ( const file of templateFiles ) {
271+ const destPath = path . join ( templatesDir , file ) ;
272+ try {
273+ await fs . access ( destPath ) ;
274+ // File exists, don't overwrite
275+ } catch {
276+ // File doesn't exist, copy from template
277+ const srcPath = path . join ( TEMPLATES_DIR , 'standard' , 'files' , 'README.md' ) ;
278+ try {
279+ await fs . copyFile ( srcPath , destPath ) ;
280+ templatesUpdated = true ;
281+ console . log ( chalk . green ( `✓ Added missing template: ${ file } ` ) ) ;
282+ } catch { }
283+ }
284+ }
285+
286+ // Save upgraded config
287+ await saveConfig ( upgradedConfig , cwd ) ;
288+
289+ console . log ( '' ) ;
290+ console . log ( chalk . green ( '✓ Configuration upgraded!' ) ) ;
291+ console . log ( '' ) ;
292+ console . log ( chalk . gray ( 'What was updated:' ) ) ;
293+ console . log ( chalk . gray ( ' - Config merged with latest defaults' ) ) ;
294+ if ( templatesUpdated ) {
295+ console . log ( chalk . gray ( ' - Missing templates added' ) ) ;
296+ }
297+ console . log ( '' ) ;
298+ console . log ( chalk . gray ( 'What was preserved:' ) ) ;
299+ console . log ( chalk . gray ( ' - Your specs/ directory' ) ) ;
300+ console . log ( chalk . gray ( ' - Your AGENTS.md' ) ) ;
301+ console . log ( chalk . gray ( ' - Your custom settings' ) ) ;
302+ console . log ( '' ) ;
303+ }
304+
98305/**
99306 * Init command - initialize LeanSpec in current directory
100307 */
101308export function initCommand ( ) : Command {
102309 return new Command ( 'init' )
103310 . description ( 'Initialize LeanSpec in current directory' )
104311 . option ( '-y, --yes' , 'Skip prompts and use defaults (quick start with standard template)' )
312+ . option ( '-f, --force' , 'Force re-initialization (resets config, keeps specs)' )
105313 . option ( '--template <name>' , 'Use specific template (standard or detailed)' )
106314 . option ( '--example [name]' , 'Scaffold an example project for tutorials (interactive if no name provided)' )
107315 . option ( '--name <dirname>' , 'Custom directory name for example project' )
108316 . option ( '--list' , 'List available example projects' )
109317 . option ( '--agent-tools <tools>' , 'AI tools to create symlinks for (comma-separated: claude,gemini,copilot or "all" or "none")' )
110- . action ( async ( options : { yes ?: boolean ; template ?: string ; example ?: string ; name ?: string ; list ?: boolean ; agentTools ?: string } ) => {
318+ . action ( async ( options : { yes ?: boolean ; force ?: boolean ; template ?: string ; example ?: string ; name ?: string ; list ?: boolean ; agentTools ?: string } ) => {
111319 if ( options . list ) {
112320 await listExamples ( ) ;
113321 return ;
@@ -118,21 +326,44 @@ export function initCommand(): Command {
118326 return ;
119327 }
120328
121- await initProject ( options . yes , options . template , options . agentTools ) ;
329+ await initProject ( options . yes , options . template , options . agentTools , options . force ) ;
122330 } ) ;
123331}
124332
125- export async function initProject ( skipPrompts = false , templateOption ?: string , agentToolsOption ?: string ) : Promise < void > {
333+ export async function initProject ( skipPrompts = false , templateOption ?: string , agentToolsOption ?: string , forceReinit = false ) : Promise < void > {
126334 const cwd = process . cwd ( ) ;
127335
128336 // Check if already initialized
337+ const configPath = path . join ( cwd , '.lean-spec' , 'config.json' ) ;
338+ let isAlreadyInitialized = false ;
339+
129340 try {
130- await fs . access ( path . join ( cwd , '.lean-spec' , 'config.json' ) ) ;
131- console . log ( chalk . yellow ( '⚠ LeanSpec already initialized in this directory.' ) ) ;
132- console . log ( chalk . gray ( 'To reinitialize, delete .lean-spec/ directory first.' ) ) ;
133- return ;
341+ await fs . access ( configPath ) ;
342+ isAlreadyInitialized = true ;
134343 } catch {
135- // Not initialized, continue
344+ // Not initialized, continue with fresh init
345+ }
346+
347+ // Handle re-initialization
348+ if ( isAlreadyInitialized ) {
349+ const strategy = await handleReinitialize ( cwd , skipPrompts , forceReinit ) ;
350+
351+ if ( strategy === 'cancel' ) {
352+ return ;
353+ }
354+
355+ if ( strategy === 'upgrade' ) {
356+ await upgradeConfig ( cwd ) ;
357+ return ;
358+ }
359+
360+ // For 'reset-config' and 'full-reset', we continue with normal init flow
361+ // but 'full-reset' will have already cleaned up the directory
362+ if ( strategy === 'reset-config' ) {
363+ // Just remove config, keep specs
364+ await fs . rm ( path . join ( cwd , '.lean-spec' ) , { recursive : true , force : true } ) ;
365+ console . log ( chalk . gray ( 'Removed .lean-spec/ configuration' ) ) ;
366+ }
136367 }
137368
138369 console . log ( '' ) ;
0 commit comments