Skip to content

Conversation

@TabishB
Copy link
Contributor

@TabishB TabishB commented Oct 25, 2025

Summary

Fixes an issue where openspec init would not recreate missing template files (like openspec/AGENTS.md) when running in extend mode.

Problem

Previously, if a user:

  1. Ran openspec init (creates openspec/ directory and all files)
  2. Deleted openspec/AGENTS.md
  3. Ran openspec init again

The command would detect the existing openspec/ directory, enter extend mode, and skip all file generation - thus not recreating the deleted file.

Solution

  • Added ensureTemplateFiles() method that checks if each template file exists before writing
  • Modified extend mode to call ensureTemplateFiles() instead of completely skipping file generation
  • Updated the message from "Skipping base scaffolding" to "Checking for missing files..." for better clarity
  • Preserves existing files while recreating any that are missing

Changes

  • src/core/init.ts: Added ensureTemplateFiles() method and updated extend mode logic
  • test/core/init.test.ts: Added tests for recreating deleted openspec/AGENTS.md and openspec/project.md

Testing

  • ✅ All existing 236 tests pass
  • ✅ Added 2 new tests specifically for this fix
  • ✅ Total: 238 tests passing

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Extend mode now creates the required directory structure and writes any missing template files while preserving existing user-modified files; the initialization message reflects this behavior.
  • Tests

    • Added tests verifying that deleted template files are recreated on re-initialization and that existing templates modified by users are not overwritten.

The init command now checks for and recreates missing template files
(like openspec/AGENTS.md and openspec/project.md) when running in
extend mode, instead of skipping file generation entirely.

Previously, if a user deleted openspec/AGENTS.md and ran init again,
the file would not be recreated because the openspec/ directory
existed, triggering extend mode which skipped all file generation.

Changes:
- Added ensureTemplateFiles() method to check and recreate missing files
- Modified extend mode to call ensureTemplateFiles() instead of skipping
- Updated message to "Checking for missing files..." for clarity
- Added tests for recreating deleted openspec/AGENTS.md and project.md
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 25, 2025

Walkthrough

In extend mode, InitCommand now ensures the OpenSpec directory structure exists and recreates any missing template files instead of skipping scaffolding. New private methods generateFiles, ensureTemplateFiles, and writeTemplateFiles were added; writeTemplateFiles accepts a skipExisting flag to preserve existing files.

Changes

Cohort / File(s) Summary
Init command - template generation
src/core/init.ts
Added private methods generateFiles(openspecPath, config), ensureTemplateFiles(openspecPath, config), and writeTemplateFiles(openspecPath, config, skipExisting). Refactored extend-mode flow to create target directories, log "Checking for missing files...", and write only missing templates when skipExisting is true.
Tests - extend mode recovery
test/core/init.test.ts
Added tests that run init, delete generated templates (openspec/AGENTS.md, openspec/project.md), then re-run init in extend mode and assert missing files are recreated with expected content; added helper testFileRecreationInExtendMode and a test ensuring existing user-modified templates are not overwritten.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant InitCommand
    participant TemplateLoader
    participant FileSystem

    User->>InitCommand: execute(extendMode=true)
    InitCommand->>FileSystem: ensureDirectoryStructure(openspec/)
    InitCommand->>InitCommand: log("Checking for missing files...")
    InitCommand->>TemplateLoader: listTemplates(config)
    loop for each template
        InitCommand->>FileSystem: exists?(targetPath)
        alt not exists
            InitCommand->>TemplateLoader: loadTemplate(templateId)
            InitCommand->>FileSystem: writeFile(targetPath, content)
            Note right of FileSystem: Missing template created
        else exists
            Note right of FileSystem: Existing file preserved
        end
    end
    InitCommand-->>User: Init complete
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Review writeTemplateFiles for correct async/await usage and error handling.
  • Confirm skipExisting semantics and safe path resolution to avoid unintended writes.
  • Validate tests for reliable filesystem isolation and deterministic assertions.

Poem

🐰 I hopped through folders, soft and bright,
Found AGENTS and project lost from sight.
I nudged the files, then wrote with care,
Restored the docs in moonlit air.
Extend mode hums — the meadow’s fair. 🥕

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The pull request title "fix: recreate missing openspec template files in extend mode" directly and accurately captures the primary change in the changeset. The main modifications in src/core/init.ts implement exactly what the title describes: addressing the issue where deleted template files were not being recreated when running openspec init in extend mode. The title is specific and informative, clearly indicating both the problem being fixed (recreating missing files) and the context (extend mode), making it easy for teammates reviewing the history to understand the intent of the change.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch TabishB/fix-init-recreate-agents

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
src/core/init.ts (1)

698-721: Consider consolidating duplicate logic with generateFiles.

The new ensureTemplateFiles method correctly implements conditional file creation, but it duplicates most of the logic from generateFiles (lines 677-696). Both methods create the same context, retrieve templates, handle function/string content, and write files—the only difference is the existence check.

Consider extracting shared logic to reduce duplication:

+  private async writeTemplateFiles(
+    openspecPath: string,
+    config: OpenSpecConfig,
+    onlyIfMissing: boolean = false
+  ): Promise<void> {
+    const context: ProjectContext = {};
+    const templates = TemplateManager.getTemplates(context);
+
+    for (const template of templates) {
+      const filePath = path.join(openspecPath, template.path);
+
+      if (onlyIfMissing && (await FileSystemUtils.fileExists(filePath))) {
+        continue;
+      }
+
+      const content =
+        typeof template.content === 'function'
+          ? template.content(context)
+          : template.content;
+
+      await FileSystemUtils.writeFile(filePath, content);
+    }
+  }

   private async generateFiles(
     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);
-      const content =
-        typeof template.content === 'function'
-          ? template.content(context)
-          : template.content;
-
-      await FileSystemUtils.writeFile(filePath, content);
-    }
+    await this.writeTemplateFiles(openspecPath, config, false);
   }

   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);
-      }
-    }
+    await this.writeTemplateFiles(openspecPath, config, true);
   }
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 92b4546 and 01fded8.

📒 Files selected for processing (2)
  • src/core/init.ts (2 hunks)
  • test/core/init.test.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
src/core/init.ts (4)
src/core/config.ts (1)
  • OpenSpecConfig (8-10)
src/core/templates/index.ts (2)
  • ProjectContext (44-44)
  • TemplateManager (13-42)
src/core/templates/project-template.ts (1)
  • ProjectContext (1-6)
src/utils/file-system.ts (1)
  • FileSystemUtils (44-187)
test/core/init.test.ts (2)
src/utils/file-system.ts (1)
  • fileExists (76-86)
test/core/update.test.ts (2)
  • updateCommand (971-983)
  • rootAgentsPath (985-1008)
🔇 Additional comments (2)
test/core/init.test.ts (1)

582-622: LGTM! Well-structured tests for extend mode file recovery.

The two new test cases properly validate that deleted template files are recreated when openspec init runs in extend mode. The tests follow the existing patterns, use appropriate assertions, and verify both file existence and content correctness.

src/core/init.ts (1)

430-434: LGTM! Extend mode now properly ensures file structure.

The updated logic correctly addresses the issue by calling createDirectoryStructure and ensureTemplateFiles in extend mode instead of skipping scaffolding entirely. The log message change accurately reflects the new behavior.

Extracted shared logic from generateFiles() and ensureTemplateFiles()
into a new writeTemplateFiles() method with a skipExisting parameter.
This eliminates code duplication while maintaining the same behavior.
@TabishB
Copy link
Contributor Author

TabishB commented Oct 25, 2025

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 25, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
src/core/init.ts (1)

691-717: LGTM! Solid implementation of conditional template writing.

The writeTemplateFiles method correctly implements the core logic for template file generation with proper async handling and clear conditional logic for skipping existing files.

Optional refinement: Consider replacing the skipExisting boolean parameter with an enum for better readability at call sites:

enum WriteMode {
  Overwrite,
  SkipExisting
}

private async writeTemplateFiles(
  openspecPath: string,
  config: OpenSpecConfig,
  mode: WriteMode
): Promise<void> {
  // ...
  if (mode === WriteMode.SkipExisting && (await FileSystemUtils.fileExists(filePath))) {
    continue;
  }
  // ...
}

However, the current boolean implementation is perfectly acceptable and clear.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 01fded8 and 499241b.

📒 Files selected for processing (1)
  • src/core/init.ts (3 hunks)
🔇 Additional comments (3)
src/core/init.ts (3)

428-435: LGTM! Extend mode now correctly regenerates missing files.

The updated logic properly addresses the issue where extend mode was skipping file generation entirely. By calling createDirectoryStructure and ensureTemplateFiles, deleted template files will now be recreated while preserving existing ones.


677-682: LGTM! Clean delegation for fresh initialization.

The generateFiles method correctly delegates to writeTemplateFiles with skipExisting: false, which is appropriate for non-extend mode where all template files should be written.


684-689: LGTM! Proper delegation for extend mode.

The ensureTemplateFiles method correctly delegates to writeTemplateFiles with skipExisting: true, ensuring that existing files are preserved while missing ones are recreated.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
src/core/init.ts (1)

631-641: Good refactor: parallel execution improves performance.

Using Promise.all with Object.fromEntries is more idiomatic and runs all tool state checks concurrently rather than sequentially. The as const assertion ensures proper tuple typing.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 499241b and d454ccf.

📒 Files selected for processing (1)
  • src/core/init.ts (4 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/core/init.ts (2)
src/core/config.ts (2)
  • AI_TOOLS (19-34)
  • OpenSpecConfig (8-10)
src/utils/file-system.ts (1)
  • FileSystemUtils (44-187)
🔇 Additional comments (2)
src/core/init.ts (2)

428-435: LGTM! Core fix correctly addresses the issue.

The extend mode now properly recreates missing template files instead of skipping scaffolding entirely. The updated message accurately reflects the new behavior, and calling createDirectoryStructure ensures consistency even if the directory structure is incomplete.


679-719: Well-structured solution with correct skipExisting logic.

The three-method design provides clear separation:

  • generateFiles: Initial mode creates all files
  • ensureTemplateFiles: Extend mode recreates only missing files
  • writeTemplateFiles: Shared implementation with flexible behavior

The conditional logic at lines 708-710 correctly preserves existing files in extend mode while recreating deleted ones.

@TabishB TabishB force-pushed the TabishB/fix-init-recreate-agents branch from d454ccf to 499241b Compare October 25, 2025 09:24
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
test/core/init.test.ts (2)

582-622: Consider extracting a helper function to reduce code duplication.

The two tests share nearly identical structure (approximately 90% code overlap). Consider extracting a reusable helper to improve maintainability:

async function testFileRecreationInExtendMode(
  testDir: string,
  initCommand: InitCommand,
  relativePath: string,
  expectedContent: string
) {
  queueSelections('claude', DONE, DONE);
  
  // First init
  await initCommand.execute(testDir);
  
  const filePath = path.join(testDir, relativePath);
  expect(await fileExists(filePath)).toBe(true);
  
  // Delete the file
  await fs.unlink(filePath);
  expect(await fileExists(filePath)).toBe(false);
  
  // Run init again - should recreate the file
  await initCommand.execute(testDir);
  expect(await fileExists(filePath)).toBe(true);
  
  const content = await fs.readFile(filePath, 'utf-8');
  expect(content).toContain(expectedContent);
}

Then simplify the tests:

it('should recreate deleted openspec/AGENTS.md in extend mode', async () => {
  await testFileRecreationInExtendMode(
    testDir,
    initCommand,
    'openspec/AGENTS.md',
    'OpenSpec Instructions'
  );
});

it('should recreate deleted openspec/project.md in extend mode', async () => {
  await testFileRecreationInExtendMode(
    testDir,
    initCommand,
    'openspec/project.md',
    'Project Context'
  );
});

582-622: Consider adding a test to verify existing files are preserved in extend mode.

The current tests validate that missing files are recreated, but there's no test confirming that existing files are NOT overwritten when skipExisting is true. This would provide stronger coverage of the extend mode behavior.

it('should preserve existing template files in extend mode', async () => {
  queueSelections('claude', DONE, DONE);
  
  // First init
  await initCommand.execute(testDir);
  
  const agentsPath = path.join(testDir, 'openspec', 'AGENTS.md');
  const customContent = '# My Custom AGENTS Content\nDo not overwrite this!';
  
  // Modify the file with custom content
  await fs.writeFile(agentsPath, customContent);
  
  // Run init again - should NOT overwrite
  await initCommand.execute(testDir);
  
  const content = await fs.readFile(agentsPath, 'utf-8');
  expect(content).toBe(customContent);
  expect(content).not.toContain('OpenSpec Instructions');
});
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d454ccf and e886801.

📒 Files selected for processing (2)
  • src/core/init.ts (3 hunks)
  • test/core/init.test.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/core/init.ts
🧰 Additional context used
🧬 Code graph analysis (1)
test/core/init.test.ts (1)
src/utils/file-system.ts (1)
  • fileExists (76-86)
🔇 Additional comments (1)
test/core/init.test.ts (1)

582-622: Well-structured tests that correctly validate the extend mode file recreation feature.

The tests properly verify that deleted template files are recreated during extend mode, following the workflow: initial init → file deletion → re-init → file recreation with correct content.

- Extracted testFileRecreationInExtendMode helper to reduce code duplication
- Added test to verify existing files are preserved in extend mode
- Ensures skipExisting behavior is properly tested
@TabishB TabishB force-pushed the TabishB/fix-init-recreate-agents branch from e886801 to f036fbe Compare October 25, 2025 09:52
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
test/core/init.test.ts (1)

1201-1225: LGTM! Well-structured helper function.

The helper properly tests the file recreation workflow with clear step-by-step assertions. The logic correctly simulates extend mode by queueing an empty selection (DONE only) for the second init run.

Minor suggestion: If additional tests need similar file recreation logic, consider extracting this to a shared test utilities module. For now, the current placement is fine.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e886801 and 300d020.

📒 Files selected for processing (2)
  • src/core/init.ts (3 hunks)
  • test/core/init.test.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/core/init.ts
🧰 Additional context used
🧬 Code graph analysis (1)
test/core/init.test.ts (1)
src/utils/file-system.ts (1)
  • fileExists (76-86)
🔇 Additional comments (2)
test/core/init.test.ts (2)

582-598: LGTM! Tests properly verify template file recreation in extend mode.

Both tests correctly validate that deleted template files (openspec/AGENTS.md and openspec/project.md) are recreated when running openspec init again in extend mode, with appropriate content assertions.


600-618: LGTM! Excellent coverage of the preservation behavior.

The test properly verifies that user-modified template files are not overwritten during subsequent init runs in extend mode. The dual assertion (line 616: exact match, line 617: no template content) provides strong validation.

@TabishB TabishB merged commit fb1d37e into main Oct 25, 2025
7 checks passed
@coderabbitai coderabbitai bot mentioned this pull request Nov 14, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants