diff --git a/tools/cli/installers/lib/core/ide-config-manager.js b/tools/cli/installers/lib/core/ide-config-manager.js index 56aa8812..4a0aa232 100644 --- a/tools/cli/installers/lib/core/ide-config-manager.js +++ b/tools/cli/installers/lib/core/ide-config-manager.js @@ -68,7 +68,9 @@ class IdeConfigManager { sortKeys: false, }); - await fs.writeFile(configPath, yamlContent, 'utf8'); + // Ensure POSIX-compliant final newline + const content = yamlContent.endsWith('\n') ? yamlContent : yamlContent + '\n'; + await fs.writeFile(manifestPath, content, 'utf8'); } /** diff --git a/tools/cli/installers/lib/core/installer.js b/tools/cli/installers/lib/core/installer.js index 21137d1b..24f746cc 100644 --- a/tools/cli/installers/lib/core/installer.js +++ b/tools/cli/installers/lib/core/installer.js @@ -906,8 +906,9 @@ class Installer { } } - // Write the clean config file - await fs.writeFile(configPath, header + yamlContent, 'utf8'); + // Write the clean config file with POSIX-compliant final newline + const content = header + yamlContent; + await fs.writeFile(configPath, content.endsWith('\n') ? content : content + '\n', 'utf8'); // Track the config file in installedFiles this.installedFiles.push(configPath); @@ -1195,8 +1196,9 @@ class Installer { // DO NOT replace {project-root} - LLMs understand this placeholder at runtime // const processedContent = xmlContent.replaceAll('{project-root}', projectDir); - // Write the built .md file to bmad/{module}/agents/ - await fs.writeFile(mdPath, xmlContent, 'utf8'); + // Write the built .md file to bmad/{module}/agents/ with POSIX-compliant final newline + const content = xmlContent.endsWith('\n') ? xmlContent : xmlContent + '\n'; + await fs.writeFile(mdPath, content, 'utf8'); this.installedFiles.push(mdPath); // Remove the source YAML file - we can regenerate from installer source if needed @@ -1213,7 +1215,9 @@ class Installer { if (content.includes(' 0) { @@ -1387,8 +1392,9 @@ class Installer { // DO NOT replace {project-root} - LLMs understand this placeholder at runtime // const processedContent = xmlContent.replaceAll('{project-root}', projectDir); - // Write the rebuilt .md file - await fs.writeFile(targetMdPath, xmlContent, 'utf8'); + // Write the rebuilt .md file with POSIX-compliant final newline + const content = xmlContent.endsWith('\n') ? xmlContent : xmlContent + '\n'; + await fs.writeFile(targetMdPath, content, 'utf8'); // Display result with customizations if any if (customizedFields.length > 0) { @@ -2005,6 +2011,11 @@ class Installer { configContent += processedTemplate; + // Ensure POSIX-compliant final newline + if (!configContent.endsWith('\n')) { + configContent += '\n'; + } + await fs.writeFile(configPath, configContent, 'utf8'); this.installedFiles.push(configPath); // Track agent config files createdCount++; diff --git a/tools/cli/installers/lib/core/manifest-generator.js b/tools/cli/installers/lib/core/manifest-generator.js index 9996e637..8a926b40 100644 --- a/tools/cli/installers/lib/core/manifest-generator.js +++ b/tools/cli/installers/lib/core/manifest-generator.js @@ -469,7 +469,9 @@ class ManifestGenerator { sortKeys: false, }); - await fs.writeFile(manifestPath, yamlStr); + // Ensure POSIX-compliant final newline + const content = yamlStr.endsWith('\n') ? yamlStr : yamlStr + '\n'; + await fs.writeFile(manifestPath, content); return manifestPath; } diff --git a/tools/cli/installers/lib/core/manifest.js b/tools/cli/installers/lib/core/manifest.js index 7410450f..e0cf1cd8 100644 --- a/tools/cli/installers/lib/core/manifest.js +++ b/tools/cli/installers/lib/core/manifest.js @@ -35,7 +35,9 @@ class Manifest { sortKeys: false, }); - await fs.writeFile(manifestPath, yamlContent, 'utf8'); + // Ensure POSIX-compliant final newline + const content = yamlContent.endsWith('\n') ? yamlContent : yamlContent + '\n'; + await fs.writeFile(manifestPath, content, 'utf8'); return { success: true, path: manifestPath, filesTracked: 0 }; } @@ -104,7 +106,9 @@ class Manifest { sortKeys: false, }); - await fs.writeFile(manifestPath, yamlContent, 'utf8'); + // Ensure POSIX-compliant final newline + const content = yamlContent.endsWith('\n') ? yamlContent : yamlContent + '\n'; + await fs.writeFile(manifestPath, content, 'utf8'); return manifest; } diff --git a/tools/cli/lib/config.js b/tools/cli/lib/config.js index ade23e9e..b3ab9cb5 100644 --- a/tools/cli/lib/config.js +++ b/tools/cli/lib/config.js @@ -33,7 +33,9 @@ class Config { }); await fs.ensureDir(path.dirname(configPath)); - await fs.writeFile(configPath, yamlContent, 'utf8'); + // Ensure POSIX-compliant final newline + const content = yamlContent.endsWith('\n') ? yamlContent : yamlContent + '\n'; + await fs.writeFile(configPath, content, 'utf8'); } /** diff --git a/tools/cli/lib/yaml-format.js b/tools/cli/lib/yaml-format.js index 553e72ae..66dbe3d9 100755 --- a/tools/cli/lib/yaml-format.js +++ b/tools/cli/lib/yaml-format.js @@ -64,7 +64,8 @@ async function formatYamlContent(content, filename) { noRefs: true, sortKeys: false, // Preserve key order }); - return formatted; + // Ensure POSIX-compliant final newline + return formatted.endsWith('\n') ? formatted : formatted + '\n'; } catch (error) { console.error(chalk.red(`❌ YAML syntax error in ${filename}:`), error.message); console.error(chalk.yellow(`💡 Try manually fixing the YAML structure first`));