aios-core
Version:
Synkra AIOS: AI-Orchestrated System for Full Stack Development - Core Framework
367 lines (323 loc) • 10.1 kB
JavaScript
/**
* Generate Install Manifest
* Dynamically generates install-manifest.yaml with file hashes for brownfield upgrades
*
* @script scripts/generate-install-manifest.js
* @story 6.18 - Dynamic Manifest & Brownfield Upgrade System
*
* Usage:
* node scripts/generate-install-manifest.js
* npm run generate:manifest
*/
const fs = require('fs-extra');
const path = require('path');
const yaml = require('js-yaml');
const { hashFile, hashString } = require('../packages/installer/src/installer/file-hasher');
// Import FOLDERS_TO_COPY from installer (same source of truth)
const FOLDERS_TO_COPY = [
// v2.1 Modular Structure
'core',
'development',
'product',
'infrastructure',
// v2.1 New Modules (Story 6.19 - added missing folders)
'workflow-intelligence',
'monitor',
// v2.0 Legacy Flat Structure (backwards compatibility)
'agents',
'agent-teams',
'checklists',
'data',
'docs',
'elicitation',
'scripts',
'tasks',
'templates',
'tools',
'workflows',
// Additional directories
'cli',
'manifests',
];
const ROOT_FILES_TO_COPY = [
'index.js',
'index.esm.js',
'index.d.ts',
'core-config.yaml',
'package.json',
'user-guide.md',
'working-in-the-brownfield.md',
];
// Files/folders to exclude from manifest
const EXCLUDE_PATTERNS = [
/node_modules/,
/\.git/,
/\.DS_Store/,
/Thumbs\.db/,
/\.installed-manifest\.yaml$/, // Don't include installed manifest
/\.bak$/,
/\.tmp$/,
/~$/,
// Gitignored legacy docs (not tracked in repo)
/docs\/standards\/AIOS-LIVRO-DE-OURO/,
/docs\/standards\/AIOS-FRAMEWORK-MASTER\.md$/,
/docs\/standards\/V3-ARCHITECTURAL-DECISIONS\.md$/,
/docs\/SHARD-TRANSLATION-GUIDE\.md$/,
/docs\/component-creation-guide\.md$/,
/docs\/template-syntax\.md$/,
/docs\/troubleshooting-guide\.md$/,
/docs\/session-update-pattern\.md$/,
// Gitignored generated files
/data\/registry-update-log\.jsonl$/,
/data\/registry-healing-log\.jsonl$/,
/infrastructure\/tests\/utilities-audit-results\.json$/,
/manifests\/agents\.csv$/,
/manifests\/tasks\.csv$/,
/manifests\/workers\.csv$/,
/index\.d\.ts$/, // Generated TypeScript definitions
];
/**
* Check if a path should be excluded
* @param {string} filePath - Path to check
* @returns {boolean} - True if should be excluded
*/
function shouldExclude(filePath) {
return EXCLUDE_PATTERNS.some((pattern) => pattern.test(filePath));
}
/**
* Determine file type based on path
* @param {string} relativePath - Relative file path
* @returns {string} - File type identifier
*/
function getFileType(relativePath) {
const normalized = relativePath.replace(/\\/g, '/');
if (normalized.includes('/agents/') || normalized.startsWith('agents/')) {
return 'agent';
}
if (normalized.includes('/tasks/') || normalized.startsWith('tasks/')) {
return 'task';
}
if (normalized.includes('/workflows/') || normalized.startsWith('workflows/')) {
return 'workflow';
}
if (normalized.includes('/templates/') || normalized.startsWith('templates/')) {
return 'template';
}
if (normalized.includes('/checklists/') || normalized.startsWith('checklists/')) {
return 'checklist';
}
if (normalized.includes('/scripts/') || normalized.startsWith('scripts/')) {
return 'script';
}
if (normalized.includes('/tools/') || normalized.startsWith('tools/')) {
return 'tool';
}
if (normalized.includes('/data/') || normalized.startsWith('data/')) {
return 'data';
}
if (normalized.includes('/docs/') || normalized.startsWith('docs/')) {
return 'documentation';
}
if (normalized.includes('/elicitation/') || normalized.startsWith('elicitation/')) {
return 'elicitation';
}
if (normalized.includes('/manifests/') || normalized.startsWith('manifests/')) {
return 'manifest';
}
if (normalized.includes('/cli/') || normalized.startsWith('cli/')) {
return 'cli';
}
if (normalized.includes('/core/') || normalized.startsWith('core/')) {
return 'core';
}
if (normalized.includes('/infrastructure/') || normalized.startsWith('infrastructure/')) {
return 'infrastructure';
}
if (normalized.includes('/product/') || normalized.startsWith('product/')) {
return 'product';
}
if (normalized.includes('/development/') || normalized.startsWith('development/')) {
return 'development';
}
if (
normalized.includes('/workflow-intelligence/') ||
normalized.startsWith('workflow-intelligence/')
) {
return 'workflow-intelligence';
}
if (normalized.includes('/monitor/') || normalized.startsWith('monitor/')) {
return 'monitor';
}
// Root files
if (normalized.endsWith('.js') || normalized.endsWith('.ts')) {
return 'code';
}
if (normalized.endsWith('.yaml') || normalized.endsWith('.yml')) {
return 'config';
}
if (normalized.endsWith('.md')) {
return 'documentation';
}
return 'other';
}
/**
* Recursively scan directory and collect file metadata
* @param {string} dirPath - Directory to scan
* @param {string} basePath - Base path for relative paths
* @param {string[]} files - Array to collect files
*/
function scanDirectory(dirPath, basePath, files = []) {
if (!fs.existsSync(dirPath)) {
return files;
}
const entries = fs.readdirSync(dirPath, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(dirPath, entry.name);
const relativePath = path.relative(basePath, fullPath).replace(/\\/g, '/');
if (shouldExclude(relativePath)) {
continue;
}
if (entry.isDirectory()) {
scanDirectory(fullPath, basePath, files);
} else if (entry.isFile()) {
files.push(fullPath);
}
}
return files;
}
/**
* Generate the install manifest
* @returns {Object} - Manifest object
*/
async function generateManifest() {
const aiosCoreDir = path.join(__dirname, '..', '.aios-core');
const packageJsonPath = path.join(__dirname, '..', 'package.json');
if (!fs.existsSync(aiosCoreDir)) {
throw new Error(`.aios-core directory not found at ${aiosCoreDir}`);
}
// Get version from package.json
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
const version = packageJson.version;
console.log(`Generating manifest for aios-core v${version}...`);
const allFiles = [];
// Scan folders
for (const folder of FOLDERS_TO_COPY) {
const folderPath = path.join(aiosCoreDir, folder);
if (fs.existsSync(folderPath)) {
scanDirectory(folderPath, aiosCoreDir, allFiles);
}
}
// Add root files
for (const file of ROOT_FILES_TO_COPY) {
const filePath = path.join(aiosCoreDir, file);
if (fs.existsSync(filePath)) {
allFiles.push(filePath);
}
}
// Note: install-manifest.yaml itself is not included in the manifest
// as it would create a circular dependency during validation
console.log(`Found ${allFiles.length} files to include in manifest`);
// Generate file entries with metadata
const fileEntries = [];
let processedCount = 0;
for (const fullPath of allFiles) {
try {
const relativePath = path.relative(aiosCoreDir, fullPath).replace(/\\/g, '/');
const stats = fs.statSync(fullPath);
const hash = hashFile(fullPath);
const fileType = getFileType(relativePath);
fileEntries.push({
path: relativePath,
hash: `sha256:${hash}`,
type: fileType,
size: stats.size,
});
processedCount++;
if (processedCount % 50 === 0) {
console.log(` Processed ${processedCount}/${allFiles.length} files...`);
}
} catch (error) {
console.warn(` Warning: Could not process ${fullPath}: ${error.message}`);
}
}
// Sort files by path for consistent output
fileEntries.sort((a, b) => a.path.localeCompare(b.path));
// Build manifest object
const manifest = {
version: version,
generated_at: new Date().toISOString(),
generator: 'scripts/generate-install-manifest.js',
file_count: fileEntries.length,
files: fileEntries,
};
return manifest;
}
/**
* Write manifest to file
* @param {Object} manifest - Manifest object
*/
async function writeManifest(manifest) {
const manifestPath = path.join(__dirname, '..', '.aios-core', 'install-manifest.yaml');
// Generate YAML with custom options for readability
const yamlContent = yaml.dump(manifest, {
indent: 2,
lineWidth: 120,
noRefs: true,
sortKeys: false,
quotingType: '"',
});
// Add header comment
const header = `# AIOS-Core Install Manifest
# Auto-generated by scripts/generate-install-manifest.js
# DO NOT EDIT MANUALLY - regenerate with: npm run generate:manifest
#
# This manifest is used for brownfield upgrades to track:
# - Which files are part of the framework
# - SHA256 hashes for change detection
# - File types for categorization
#
`;
fs.writeFileSync(manifestPath, header + yamlContent, 'utf8');
console.log(`\nManifest written to: ${manifestPath}`);
console.log(` Version: ${manifest.version}`);
console.log(` Files: ${manifest.file_count}`);
console.log(` Generated: ${manifest.generated_at}`);
// Also compute and display manifest hash for integrity verification
const manifestHash = hashString(yamlContent);
console.log(` Manifest hash: sha256:${manifestHash.substring(0, 16)}...`);
return manifestPath;
}
/**
* Main execution
*/
async function main() {
try {
console.log('='.repeat(60));
console.log('AIOS-Core Install Manifest Generator');
console.log('='.repeat(60));
console.log('');
const manifest = await generateManifest();
await writeManifest(manifest);
console.log('\n✅ Manifest generated successfully!');
process.exit(0);
} catch (error) {
console.error('\n❌ Error generating manifest:', error.message);
if (process.env.DEBUG) {
console.error(error.stack);
}
process.exit(1);
}
}
// Run if called directly
if (require.main === module) {
main();
}
module.exports = {
generateManifest,
writeManifest,
getFileType,
scanDirectory,
FOLDERS_TO_COPY,
ROOT_FILES_TO_COPY,
};