Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 28 additions & 1 deletion src/core/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -427,9 +427,11 @@ export class InitCommand {
} else {
ora({ stream: process.stdout }).info(
PALETTE.midGray(
'ℹ OpenSpec already initialized. Skipping base scaffolding.'
'ℹ OpenSpec already initialized. Checking for missing files...'
)
);
await this.createDirectoryStructure(openspecPath);
await this.ensureTemplateFiles(openspecPath, config);
}

// Step 2: Configure AI tools
Expand Down Expand Up @@ -693,6 +695,31 @@ export class InitCommand {
}
}

private async ensureTemplateFiles(
openspecPath: string,
config: OpenSpecConfig
): Promise<void> {
const context: ProjectContext = {
// Could be enhanced with prompts for project details
};

const templates = TemplateManager.getTemplates(context);

for (const template of templates) {
const filePath = path.join(openspecPath, template.path);

// Only write if file doesn't exist
if (!(await FileSystemUtils.fileExists(filePath))) {
const content =
typeof template.content === 'function'
? template.content(context)
: template.content;

await FileSystemUtils.writeFile(filePath, content);
}
}
}

private async configureAITools(
projectPath: string,
openspecDir: string,
Expand Down
42 changes: 42 additions & 0 deletions test/core/init.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -579,6 +579,48 @@ describe('InitCommand', () => {
await expect(initCommand.execute(testDir)).resolves.toBeUndefined();
});

it('should recreate deleted openspec/AGENTS.md in extend mode', async () => {
queueSelections('claude', DONE, DONE);

// First init
await initCommand.execute(testDir);

const openspecAgentsPath = path.join(testDir, 'openspec', 'AGENTS.md');
expect(await fileExists(openspecAgentsPath)).toBe(true);

// Delete the file
await fs.unlink(openspecAgentsPath);
expect(await fileExists(openspecAgentsPath)).toBe(false);

// Run init again - should recreate the file
await initCommand.execute(testDir);
expect(await fileExists(openspecAgentsPath)).toBe(true);

const content = await fs.readFile(openspecAgentsPath, 'utf-8');
expect(content).toContain('OpenSpec Instructions');
});

it('should recreate deleted openspec/project.md in extend mode', async () => {
queueSelections('claude', DONE, DONE);

// First init
await initCommand.execute(testDir);

const projectMdPath = path.join(testDir, 'openspec', 'project.md');
expect(await fileExists(projectMdPath)).toBe(true);

// Delete the file
await fs.unlink(projectMdPath);
expect(await fileExists(projectMdPath)).toBe(false);

// Run init again - should recreate the file
await initCommand.execute(testDir);
expect(await fileExists(projectMdPath)).toBe(true);

const content = await fs.readFile(projectMdPath, 'utf-8');
expect(content).toContain('Project Context');
});

it('should handle non-existent target directory', async () => {
queueSelections('claude', DONE);

Expand Down
Loading