aios-core
Version:
Synkra AIOS: AI-Orchestrated System for Full Stack Development - Core Framework
208 lines (183 loc) • 6.88 kB
JavaScript
/**
* @fileoverview Merge strategy for Markdown files with AIOS-MANAGED sections
* @module merger/strategies/markdown-merger
*/
const { BaseMerger } = require('./base-merger.js');
const { createMergeResult, createEmptyStats } = require('../types.js');
const { parseMarkdownSections, hasAiosMarkers } = require('../parsers/markdown-section-parser.js');
/**
* Merge strategy for Markdown files (CLAUDE.md, rules.md).
* Uses AIOS-MANAGED markers to identify sections that can be updated.
* Preserves user sections that don't have markers.
* @extends BaseMerger
*/
class MarkdownMerger extends BaseMerger {
name = 'markdown';
/**
* Can merge if existing content has AIOS markers
* @param {string} existingContent - Existing markdown content
* @returns {boolean} True if merge is possible
*/
canMerge(existingContent) {
return hasAiosMarkers(existingContent);
}
/**
* Merge existing markdown with template
* - Updates AIOS-MANAGED sections with new content
* - Preserves user sections (without markers)
* - Adds new AIOS sections at the end
* @param {string} existingContent - Existing markdown content
* @param {string} newContent - Template markdown content
* @param {import('../types.js').MergeOptions} [options] - Merge options
* @returns {Promise<import('../types.js').MergeResult>} Merge result
*/
async merge(existingContent, newContent, options = {}) {
const existing = parseMarkdownSections(existingContent);
const template = parseMarkdownSections(newContent);
// If existing file has no AIOS markers, do legacy migration
if (!existing.hasAiosMarkers) {
return this.migrateLegacy(existingContent, template, options);
}
const stats = createEmptyStats();
const changes = [];
const processedAiosSections = new Set();
// Start building merged content
let merged = '';
// Add preamble if exists
if (existing.preamble.length > 0) {
merged += existing.preamble.join('\n');
if (!merged.endsWith('\n')) {
merged += '\n';
}
}
// Process each section from existing file
for (const section of existing.sections) {
if (section.managed) {
// AIOS-managed section - update with template content if available
const templateSection = template.sections.find(
s => s.managed && s.id === section.id,
);
if (templateSection) {
// Update with new content from template
merged += `<!-- AIOS-MANAGED-START: ${section.id} -->\n`;
merged += templateSection.lines.join('\n');
if (!merged.endsWith('\n')) {
merged += '\n';
}
merged += `<!-- AIOS-MANAGED-END: ${section.id} -->\n\n`;
processedAiosSections.add(section.id);
changes.push({
type: 'updated',
identifier: section.id,
});
stats.updated++;
} else {
// Section not in template anymore - keep existing
merged += `<!-- AIOS-MANAGED-START: ${section.id} -->\n`;
merged += section.lines.join('\n');
if (!merged.endsWith('\n')) {
merged += '\n';
}
merged += `<!-- AIOS-MANAGED-END: ${section.id} -->\n\n`;
changes.push({
type: 'preserved',
identifier: section.id,
reason: 'section not in template (possibly deprecated)',
});
stats.preserved++;
}
} else {
// User section - preserve as-is
merged += section.lines.join('\n');
if (!merged.endsWith('\n')) {
merged += '\n';
}
merged += '\n';
changes.push({
type: 'preserved',
identifier: section.id || section.title || 'unnamed-section',
});
stats.preserved++;
}
}
// Add new AIOS sections that don't exist in current file
const newSections = template.sections.filter(
s => s.managed && !processedAiosSections.has(s.id),
);
if (newSections.length > 0) {
merged += '---\n\n';
merged += '<!-- New AIOS sections added -->\n\n';
for (const section of newSections) {
merged += `<!-- AIOS-MANAGED-START: ${section.id} -->\n`;
merged += section.lines.join('\n');
if (!merged.endsWith('\n')) {
merged += '\n';
}
merged += `<!-- AIOS-MANAGED-END: ${section.id} -->\n\n`;
changes.push({
type: 'added',
identifier: section.id,
});
stats.added++;
}
}
// Clean up excessive newlines
merged = merged.replace(/\n{4,}/g, '\n\n\n').trim() + '\n';
return createMergeResult(merged, stats, changes);
}
/**
* Migrate a legacy file (without AIOS markers) by appending AIOS sections
* @param {string} existingContent - Existing content without markers
* @param {import('../parsers/markdown-section-parser.js').ParsedMarkdownFile} template - Parsed template
* @param {import('../types.js').MergeOptions} [options] - Merge options
* @returns {Promise<import('../types.js').MergeResult>} Merge result with legacy flag
*/
async migrateLegacy(existingContent, template, _options = {}) {
const stats = createEmptyStats();
const changes = [];
// Get all AIOS-managed sections from template
const aiosSections = template.sections.filter(s => s.managed);
// Start with existing content
let merged = existingContent;
if (!merged.endsWith('\n')) {
merged += '\n';
}
// Mark existing content as preserved
changes.push({
type: 'preserved',
identifier: 'existing-content',
reason: 'legacy file - all content preserved',
});
stats.preserved = 1;
// Add separator and AIOS sections
if (aiosSections.length > 0) {
merged += '\n---\n\n';
merged += '<!-- AIOS-MANAGED SECTIONS -->\n';
merged += '<!-- These sections are managed by AIOS. Edit content between markers carefully. -->\n';
merged += '<!-- Your custom content above will be preserved during updates. -->\n\n';
for (const section of aiosSections) {
merged += `<!-- AIOS-MANAGED-START: ${section.id} -->\n`;
merged += section.lines.join('\n');
if (!merged.endsWith('\n')) {
merged += '\n';
}
merged += `<!-- AIOS-MANAGED-END: ${section.id} -->\n\n`;
changes.push({
type: 'added',
identifier: section.id,
});
stats.added++;
}
}
const result = createMergeResult(merged.trim() + '\n', stats, changes);
result.isLegacyMigration = true;
return result;
}
/**
* @returns {string} Description of this strategy
*/
getDescription() {
return 'Merges Markdown files using AIOS-MANAGED section markers';
}
}
module.exports = { MarkdownMerger };