we only need one yaml lib

This commit is contained in:
Brian Madison 2025-12-13 18:35:07 +08:00
parent ce42d56fdd
commit 8642553bd7
27 changed files with 130 additions and 96 deletions

View File

@ -31,7 +31,6 @@ npm install -D @seontechnologies/playwright-utils
- `@playwright/test` >= 1.54.1 (required)
- `ajv` >= 8.0.0 (optional - for JSON Schema validation)
- `js-yaml` >= 4.0.0 (optional - for YAML schema support)
- `zod` >= 3.0.0 (optional - for Zod schema validation)
## Available Utilities

View File

@ -269,7 +269,7 @@ The validation is integrated into the GitHub Actions workflow:
## Dependencies
- **zod**: Schema validation library
- **js-yaml**: YAML parsing
- **yaml**: YAML parsing
- **glob**: File pattern matching
- **c8**: Code coverage reporting

View File

@ -10,7 +10,7 @@
const fs = require('node:fs');
const path = require('node:path');
const yaml = require('js-yaml');
const yaml = require('yaml');
const { validateAgentFile } = require('../tools/schema/agent.js');
const { glob } = require('glob');
@ -182,7 +182,7 @@ function runTest(filePath) {
let agentData;
try {
agentData = yaml.load(fileContent);
agentData = yaml.parse(fileContent);
} catch (parseError) {
// YAML parse error
if (shouldPass) {

View File

@ -1,6 +1,6 @@
const path = require('node:path');
const fs = require('fs-extra');
const yaml = require('js-yaml');
const yaml = require('yaml');
const chalk = require('chalk');
const inquirer = require('inquirer');
const { getProjectRoot, getModulePath } = require('../../../lib/project-root');
@ -109,7 +109,7 @@ class ConfigCollector {
if (await fs.pathExists(moduleConfigPath)) {
try {
const content = await fs.readFile(moduleConfigPath, 'utf8');
const moduleConfig = yaml.load(content);
const moduleConfig = yaml.parse(content);
if (moduleConfig) {
this.existingConfig[entry.name] = moduleConfig;
foundAny = true;
@ -238,7 +238,7 @@ class ConfigCollector {
}
const configContent = await fs.readFile(configPath, 'utf8');
const moduleConfig = yaml.load(configContent);
const moduleConfig = yaml.parse(configContent);
if (!moduleConfig) {
return false;
@ -514,7 +514,7 @@ class ConfigCollector {
}
const configContent = await fs.readFile(configPath, 'utf8');
const moduleConfig = yaml.load(configContent);
const moduleConfig = yaml.parse(configContent);
if (!moduleConfig) {
return;

View File

@ -31,7 +31,7 @@ class CustomModuleCache {
}
const content = await fs.readFile(this.manifestPath, 'utf8');
const yaml = require('js-yaml');
const yaml = require('yaml');
return yaml.parse(content) || {};
}
@ -39,11 +39,10 @@ class CustomModuleCache {
* Update cache manifest
*/
async updateCacheManifest(manifest) {
const yaml = require('js-yaml');
const content = yaml.dump(manifest, {
const yaml = require('yaml');
const content = yaml.stringify(manifest, {
indent: 2,
lineWidth: -1,
noRefs: true,
lineWidth: 0,
sortKeys: false,
});

View File

@ -2,7 +2,7 @@ const fs = require('fs-extra');
const path = require('node:path');
const glob = require('glob');
const chalk = require('chalk');
const yaml = require('js-yaml');
const yaml = require('yaml');
/**
* Dependency Resolver for BMAD modules
@ -147,7 +147,7 @@ class DependencyResolver {
// Quote values with backticks to make them valid YAML
yamlContent = yamlContent.replaceAll(/: `([^`]+)`/g, ': "$1"');
const frontmatter = yaml.load(yamlContent);
const frontmatter = yaml.parse(yamlContent);
if (frontmatter.dependencies) {
const deps = Array.isArray(frontmatter.dependencies) ? frontmatter.dependencies : [frontmatter.dependencies];

View File

@ -1,6 +1,6 @@
const path = require('node:path');
const fs = require('fs-extra');
const yaml = require('js-yaml');
const yaml = require('yaml');
const { Manifest } = require('./manifest');
class Detector {
@ -237,7 +237,7 @@ class Detector {
return false;
}
try {
const yaml = require('js-yaml');
const yaml = require('yaml');
const manifestContent = await fs.readFile(manifestPath, 'utf8');
const manifest = yaml.parse(manifestContent);
// V6+ manifest has installation.version

View File

@ -1,6 +1,6 @@
const path = require('node:path');
const fs = require('fs-extra');
const yaml = require('js-yaml');
const yaml = require('yaml');
/**
* Manages IDE configuration persistence
@ -61,10 +61,9 @@ class IdeConfigManager {
configuration: configuration || {},
};
const yamlContent = yaml.dump(configData, {
const yamlContent = yaml.stringify(configData, {
indent: 2,
lineWidth: -1,
noRefs: true,
lineWidth: 0,
sortKeys: false,
});
@ -88,7 +87,7 @@ class IdeConfigManager {
try {
const content = await fs.readFile(configPath, 'utf8');
const config = yaml.load(content);
const config = yaml.parse(content);
return config;
} catch (error) {
console.warn(`Warning: Failed to load IDE config for ${ideName}:`, error.message);

View File

@ -18,6 +18,7 @@ const { CLIUtils } = require('../../../lib/cli-utils');
const { ManifestGenerator } = require('./manifest-generator');
const { IdeConfigManager } = require('./ide-config-manager');
const { CustomHandler } = require('../custom/handler');
const { filterCustomizationData } = require('../../../lib/agent/compiler');
class Installer {
constructor() {
@ -1457,7 +1458,7 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
* @param {Object} moduleConfigs - Collected configuration values
*/
async generateModuleConfigs(bmadDir, moduleConfigs) {
const yaml = require('js-yaml');
const yaml = require('yaml');
// Extract core config values to share with other modules
const coreConfig = moduleConfigs.core || {};
@ -1504,11 +1505,10 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
}
// Convert config to YAML
let yamlContent = yaml.dump(finalConfig, {
let yamlContent = yaml.stringify(finalConfig, {
indent: 2,
lineWidth: -1,
noRefs: true,
sortKeys: false,
lineWidth: 0,
minContentWidth: 0,
});
// If we have core values, reorganize the YAML to group them with their comment
@ -1949,7 +1949,7 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
if (customizeExists) {
const customizeContent = await fs.readFile(customizePath, 'utf8');
const yaml = require('js-yaml');
const yaml = require('yaml');
const customizeYaml = yaml.parse(customizeContent);
// Detect what fields are customized (similar to rebuildAgentFiles)
@ -2040,8 +2040,8 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
if (customizeExists) {
const customizeContent = await fs.readFile(customizePath, 'utf8');
const yaml = require('js-yaml');
const customizeYaml = yaml.load(customizeContent);
const yaml = require('yaml');
const customizeYaml = yaml.parse(customizeContent);
// Detect what fields are customized
if (customizeYaml) {
@ -2085,18 +2085,21 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
customizeData = yaml.parse(customizeContent);
}
// Build agent answers from customize data
// Build agent answers from customize data (filter empty values)
const answers = {};
if (customizeData.persona) {
Object.assign(answers, customizeData.persona);
Object.assign(answers, filterCustomizationData(customizeData.persona));
}
if (customizeData.agent?.metadata) {
Object.assign(answers, { metadata: customizeData.agent.metadata });
const filteredMetadata = filterCustomizationData(customizeData.agent.metadata);
if (Object.keys(filteredMetadata).length > 0) {
Object.assign(answers, { metadata: filteredMetadata });
}
if (customizeData.critical_actions) {
}
if (customizeData.critical_actions && customizeData.critical_actions.length > 0) {
answers.critical_actions = customizeData.critical_actions;
}
if (customizeData.memories) {
if (customizeData.memories && customizeData.memories.length > 0) {
answers.memories = customizeData.memories;
}
@ -2153,8 +2156,8 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
let manifest = null;
if (await fs.pathExists(manifestPath)) {
const manifestContent = await fs.readFile(manifestPath, 'utf8');
const yaml = require('js-yaml');
manifest = yaml.load(manifestContent);
const yaml = require('yaml');
manifest = yaml.parse(manifestContent);
installedModules = manifest.modules || [];
}

View File

@ -1,6 +1,6 @@
const path = require('node:path');
const fs = require('fs-extra');
const yaml = require('js-yaml');
const yaml = require('yaml');
const crypto = require('node:crypto');
const { getSourcePath, getModulePath } = require('../../../lib/project-root');
@ -480,10 +480,9 @@ class ManifestGenerator {
ides: this.selectedIdes,
};
const yamlStr = yaml.dump(manifest, {
const yamlStr = yaml.stringify(manifest, {
indent: 2,
lineWidth: -1,
noRefs: true,
lineWidth: 0,
sortKeys: false,
});

View File

@ -11,7 +11,7 @@ class Manifest {
*/
async create(bmadDir, data, installedFiles = []) {
const manifestPath = path.join(bmadDir, '_cfg', 'manifest.yaml');
const yaml = require('js-yaml');
const yaml = require('yaml');
// Ensure _cfg directory exists
await fs.ensureDir(path.dirname(manifestPath));
@ -28,10 +28,9 @@ class Manifest {
};
// Write YAML manifest
const yamlContent = yaml.dump(manifestData, {
const yamlContent = yaml.stringify(manifestData, {
indent: 2,
lineWidth: -1,
noRefs: true,
lineWidth: 0,
sortKeys: false,
});
@ -48,12 +47,12 @@ class Manifest {
*/
async read(bmadDir) {
const yamlPath = path.join(bmadDir, '_cfg', 'manifest.yaml');
const yaml = require('js-yaml');
const yaml = require('yaml');
if (await fs.pathExists(yamlPath)) {
try {
const content = await fs.readFile(yamlPath, 'utf8');
const manifestData = yaml.load(content);
const manifestData = yaml.parse(content);
// Flatten the structure for compatibility with existing code
return {
@ -79,7 +78,7 @@ class Manifest {
* @param {Array} installedFiles - Updated list of installed files
*/
async update(bmadDir, updates, installedFiles = null) {
const yaml = require('js-yaml');
const yaml = require('yaml');
const manifest = (await this.read(bmadDir)) || {};
// Merge updates
@ -101,10 +100,9 @@ class Manifest {
const manifestPath = path.join(bmadDir, '_cfg', 'manifest.yaml');
await fs.ensureDir(path.dirname(manifestPath));
const yamlContent = yaml.dump(manifestData, {
const yamlContent = yaml.stringify(manifestData, {
indent: 2,
lineWidth: -1,
noRefs: true,
lineWidth: 0,
sortKeys: false,
});
@ -526,9 +524,9 @@ class Manifest {
try {
if (await fs.pathExists(configPath)) {
const yaml = require('js-yaml');
const yaml = require('yaml');
const content = await fs.readFile(configPath, 'utf8');
configs[moduleName] = yaml.load(content);
configs[moduleName] = yaml.parse(content);
}
} catch (error) {
console.warn(`Could not load config for module ${moduleName}:`, error.message);

View File

@ -1,7 +1,7 @@
const path = require('node:path');
const fs = require('fs-extra');
const chalk = require('chalk');
const yaml = require('js-yaml');
const yaml = require('yaml');
const { FileOps } = require('../../../lib/file-ops');
const { XmlHandler } = require('../../../lib/xml-handler');

View File

@ -346,7 +346,7 @@ class BaseIdeSetup {
} else if (entry.isFile() && entry.name === 'workflow.yaml') {
// Read workflow.yaml to get name and standalone property
try {
const yaml = require('js-yaml');
const yaml = require('yaml');
const content = await fs.readFile(fullPath, 'utf8');
const workflowData = yaml.parse(content);
@ -454,7 +454,7 @@ class BaseIdeSetup {
// Check for standalone: true in YAML frontmatter
const frontmatterMatch = content.match(/^---\s*\n([\s\S]*?)\n---/);
if (frontmatterMatch) {
const yaml = require('js-yaml');
const yaml = require('yaml');
try {
const frontmatter = yaml.parse(frontmatterMatch[1]);
standalone = frontmatter.standalone === true;

View File

@ -45,7 +45,7 @@ class AntigravitySetup extends BaseIdeSetup {
const injectionConfigPath = path.join(sourceModulesPath, moduleName, 'sub-modules', 'antigravity', 'injections.yaml');
if (await this.exists(injectionConfigPath)) {
const yaml = require('js-yaml');
const yaml = require('yaml');
try {
// Load injection configuration

View File

@ -44,7 +44,7 @@ class ClaudeCodeSetup extends BaseIdeSetup {
const injectionConfigPath = path.join(sourceModulesPath, moduleName, 'sub-modules', 'claude-code', 'injections.yaml');
if (await this.exists(injectionConfigPath)) {
const yaml = require('js-yaml');
const yaml = require('yaml');
try {
// Load injection configuration

View File

@ -1,6 +1,6 @@
const path = require('node:path');
const fs = require('fs-extra');
const yaml = require('js-yaml');
const yaml = require('yaml');
const { BaseIdeSetup } = require('./_base-ide');
const chalk = require('chalk');
const { AgentCommandGenerator } = require('./shared/agent-command-generator');

View File

@ -2,7 +2,7 @@ const path = require('node:path');
const { BaseIdeSetup } = require('./_base-ide');
const chalk = require('chalk');
const fs = require('fs-extra');
const yaml = require('js-yaml');
const yaml = require('yaml');
/**
* Kiro CLI setup handler for BMad Method

View File

@ -2,7 +2,7 @@ const path = require('node:path');
const fs = require('fs-extra');
const os = require('node:os');
const chalk = require('chalk');
const yaml = require('js-yaml');
const yaml = require('yaml');
const { BaseIdeSetup } = require('./_base-ide');
const { WorkflowCommandGenerator } = require('./shared/workflow-command-generator');
const { TaskToolCommandGenerator } = require('./shared/task-tool-command-generator');

View File

@ -1,6 +1,6 @@
const path = require('node:path');
const fs = require('fs-extra');
const yaml = require('js-yaml');
const yaml = require('yaml');
const { glob } = require('glob');
const { getSourcePath } = require('../../../../lib/project-root');

View File

@ -1,9 +1,10 @@
const path = require('node:path');
const fs = require('fs-extra');
const yaml = require('js-yaml');
const yaml = require('yaml');
const chalk = require('chalk');
const { XmlHandler } = require('../../../lib/xml-handler');
const { getProjectRoot, getSourcePath, getModulePath } = require('../../../lib/project-root');
const { filterCustomizationData } = require('../../../lib/agent/compiler');
/**
* Manages the installation, updating, and removal of BMAD modules.
@ -12,7 +13,7 @@ const { getProjectRoot, getSourcePath, getModulePath } = require('../../../lib/p
*
* @class ModuleManager
* @requires fs-extra
* @requires js-yaml
* @requires yaml
* @requires chalk
* @requires XmlHandler
*
@ -312,7 +313,7 @@ class ModuleManager {
// Read module config for metadata
try {
const configContent = await fs.readFile(configPath, 'utf8');
const config = yaml.load(configContent);
const config = yaml.parse(configContent);
// Use the code property as the id if available
if (config.code) {
@ -387,7 +388,7 @@ class ModuleManager {
if (configPath) {
try {
const configContent = await fs.readFile(configPath, 'utf8');
const config = yaml.load(configContent);
const config = yaml.parse(configContent);
if (config.code === moduleName) {
return modulePath;
}
@ -427,14 +428,14 @@ class ModuleManager {
if (await fs.pathExists(rootCustomConfigPath)) {
try {
const customContent = await fs.readFile(rootCustomConfigPath, 'utf8');
customConfig = yaml.load(customContent);
customConfig = yaml.parse(customContent);
} catch (error) {
console.warn(chalk.yellow(`Warning: Failed to read custom.yaml for ${moduleName}:`, error.message));
}
} else if (await fs.pathExists(moduleInstallerCustomPath)) {
try {
const customContent = await fs.readFile(moduleInstallerCustomPath, 'utf8');
customConfig = yaml.load(customContent);
customConfig = yaml.parse(customContent);
} catch (error) {
console.warn(chalk.yellow(`Warning: Failed to read custom.yaml for ${moduleName}:`, error.message));
}
@ -569,7 +570,7 @@ class ModuleManager {
if (await fs.pathExists(configPath)) {
try {
const configContent = await fs.readFile(configPath, 'utf8');
const config = yaml.load(configContent);
const config = yaml.parse(configContent);
Object.assign(moduleInfo, config);
} catch (error) {
console.warn(`Failed to read installed module config:`, error.message);
@ -700,7 +701,7 @@ class ModuleManager {
try {
// First check if web_bundle exists by parsing
const workflowConfig = yaml.load(yamlContent);
const workflowConfig = yaml.parse(yamlContent);
if (workflowConfig.web_bundle === undefined) {
// No web_bundle section, just write (placeholders already replaced above)
@ -827,20 +828,23 @@ class ModuleManager {
let answers = {};
if (await fs.pathExists(customizePath)) {
const customizeContent = await fs.readFile(customizePath, 'utf8');
const customizeData = yaml.load(customizeContent);
const customizeData = yaml.parse(customizeContent);
customizedFields = customizeData.customized_fields || [];
// Build answers object from customizations
// Build answers object from customizations (filter empty values)
if (customizeData.persona) {
Object.assign(answers, customizeData.persona);
Object.assign(answers, filterCustomizationData(customizeData.persona));
}
if (customizeData.agent?.metadata) {
Object.assign(answers, { metadata: customizeData.agent.metadata });
const filteredMetadata = filterCustomizationData(customizeData.agent.metadata);
if (Object.keys(filteredMetadata).length > 0) {
Object.assign(answers, { metadata: filteredMetadata });
}
if (customizeData.critical_actions) {
}
if (customizeData.critical_actions && customizeData.critical_actions.length > 0) {
answers.critical_actions = customizeData.critical_actions;
}
if (customizeData.memories) {
if (customizeData.memories && customizeData.memories.length > 0) {
answers.memories = customizeData.memories;
}
}
@ -852,7 +856,7 @@ class ModuleManager {
if (await fs.pathExists(coreConfigPath)) {
const yaml = require('yaml');
const coreConfigContent = await fs.readFile(coreConfigPath, 'utf8');
coreConfig = yaml.load(coreConfigContent);
coreConfig = yaml.parse(coreConfigContent);
}
// Check if agent has sidecar
@ -1017,7 +1021,7 @@ class ModuleManager {
for (const agentFile of yamlFiles) {
const agentPath = path.join(sourceAgentsPath, agentFile);
const agentYaml = yaml.load(await fs.readFile(agentPath, 'utf8'));
const agentYaml = yaml.parse(await fs.readFile(agentPath, 'utf8'));
// Check if agent has menu items with workflow-install
const menuItems = agentYaml?.agent?.menu || [];

View File

@ -1,4 +1,4 @@
const yaml = require('js-yaml');
const yaml = require('yaml');
const fs = require('fs-extra');
/**
@ -91,7 +91,7 @@ class AgentAnalyzer {
*/
async analyzeAgentFile(filePath) {
const content = await fs.readFile(filePath, 'utf8');
const agentYaml = yaml.load(content);
const agentYaml = yaml.parse(content);
return this.analyzeAgentObject(agentYaml);
}

View File

@ -313,7 +313,11 @@ async function compileAgent(yamlContent, answers = {}, agentName = '', targetPat
// Apply customization merges before template processing
// Handle metadata overrides (like name)
if (answers.metadata) {
agentYaml.agent.metadata = { ...agentYaml.agent.metadata, ...answers.metadata };
// Filter out empty values from metadata
const filteredMetadata = filterCustomizationData(answers.metadata);
if (Object.keys(filteredMetadata).length > 0) {
agentYaml.agent.metadata = { ...agentYaml.agent.metadata, ...filteredMetadata };
}
// Remove from answers so it doesn't get processed as template variables
const { metadata, ...templateAnswers } = answers;
answers = templateAnswers;
@ -353,6 +357,36 @@ async function compileAgent(yamlContent, answers = {}, agentName = '', targetPat
};
}
/**
* Filter customization data to remove empty/null values
* @param {Object} data - Raw customization data
* @returns {Object} Filtered customization data
*/
function filterCustomizationData(data) {
const filtered = {};
for (const [key, value] of Object.entries(data)) {
if (value === null || value === undefined || value === '') {
continue; // Skip null/undefined/empty values
}
if (Array.isArray(value)) {
if (value.length > 0) {
filtered[key] = value;
}
} else if (typeof value === 'object') {
const nested = filterCustomizationData(value);
if (Object.keys(nested).length > 0) {
filtered[key] = nested;
}
} else {
filtered[key] = value;
}
}
return filtered;
}
/**
* Process TTS injection markers in content
* @param {string} content - Content to process
@ -431,4 +465,5 @@ module.exports = {
buildPersonaXml,
buildPromptsXml,
buildMenuXml,
filterCustomizationData,
};

View File

@ -1,5 +1,5 @@
const fs = require('fs-extra');
const yaml = require('js-yaml');
const yaml = require('yaml');
const path = require('node:path');
const packageJson = require('../../../package.json');
@ -18,7 +18,7 @@ class Config {
}
const content = await fs.readFile(configPath, 'utf8');
return yaml.load(content);
return yaml.parse(content);
}
/**

View File

@ -1,6 +1,6 @@
const fs = require('fs-extra');
const path = require('node:path');
const yaml = require('js-yaml');
const yaml = require('yaml');
const { getProjectRoot } = require('./project-root');
/**
@ -20,7 +20,7 @@ class PlatformCodes {
try {
if (fs.existsSync(this.configPath)) {
const content = fs.readFileSync(this.configPath, 'utf8');
this.config = yaml.load(content);
this.config = yaml.parse(content);
} else {
console.warn(`Platform codes config not found at ${this.configPath}`);
this.config = { platforms: {} };

View File

@ -1,6 +1,6 @@
const fs = require('node:fs');
const path = require('node:path');
const yaml = require('js-yaml');
const yaml = require('yaml');
const { execSync } = require('node:child_process');
// Dynamic import for ES module
@ -57,11 +57,10 @@ async function formatYamlContent(content, filename) {
}
// Parse and re-dump YAML to format it
const parsed = yaml.load(fixedContent);
const formatted = yaml.dump(parsed, {
const parsed = yaml.parse(fixedContent);
const formatted = yaml.stringify(parsed, {
indent: 2,
lineWidth: -1, // Disable line wrapping
noRefs: true,
lineWidth: 0, // Disable line wrapping
sortKeys: false, // Preserve key order
});
// Ensure POSIX-compliant final newline
@ -96,7 +95,6 @@ async function processMarkdownFile(filePath) {
const [fullMatch, yamlContent] = match;
const formatted = await formatYamlContent(yamlContent, filePath);
if (formatted !== null) {
// Remove trailing newline that js-yaml adds
const trimmedFormatted = formatted.replace(/\n$/, '');
if (trimmedFormatted !== yamlContent) {

View File

@ -1,4 +1,4 @@
const yaml = require('js-yaml');
const yaml = require('yaml');
const fs = require('fs-extra');
const path = require('node:path');
const crypto = require('node:crypto');
@ -64,13 +64,13 @@ class YamlXmlBuilder {
async loadAndMergeAgent(agentYamlPath, customizeYamlPath = null) {
// Load base agent
const agentContent = await fs.readFile(agentYamlPath, 'utf8');
const agentYaml = yaml.load(agentContent);
const agentYaml = yaml.parse(agentContent);
// Load customization if exists
let merged = agentYaml;
if (customizeYamlPath && (await fs.pathExists(customizeYamlPath))) {
const customizeContent = await fs.readFile(customizeYamlPath, 'utf8');
const customizeYaml = yaml.load(customizeContent);
const customizeYaml = yaml.parse(customizeContent);
if (customizeYaml) {
// Special handling: persona fields are merged, but only non-empty values override

View File

@ -12,7 +12,7 @@
*/
const { glob } = require('glob');
const yaml = require('js-yaml');
const yaml = require('yaml');
const fs = require('node:fs');
const path = require('node:path');
const { validateAgentFile } = require('./schema/agent.js');
@ -49,7 +49,7 @@ async function main(customProjectRoot) {
try {
const fileContent = fs.readFileSync(filePath, 'utf8');
const agentData = yaml.load(fileContent);
const agentData = yaml.parse(fileContent);
// Convert absolute path to relative src/ path for module detection
const srcRelativePath = relativePath.startsWith('src/') ? relativePath : path.relative(project_root, filePath).replaceAll('\\', '/');