UNPKG

aiwg

Version:

Deployment tool and support utility for AI context. Copies agents, skills, commands, rules, and behaviors into the paths each AI platform reads (Claude Code, Codex, Copilot, Cursor, Warp, OpenClaw, and 6 more) so one source of truth works across 10 platfo

225 lines 7.88 kB
/** * Archival service for OAIS-compliant packaging * * @module research/services/archival */ import { promises as fs } from 'fs'; import { join, dirname } from 'path'; import { createHash } from 'crypto'; /** * Archival service for OAIS package management */ export class ArchivalService { archiveDir; formatVersion; constructor(config = {}) { this.archiveDir = config.archiveDir || '.aiwg/research/archives'; this.formatVersion = config.formatVersion || '1.0'; } /** * Create OAIS archive package */ async createPackage(sources, type) { const id = this.generatePackageId(type); const packagePath = join(this.archiveDir, type.toLowerCase(), id); // Create package directory await this.ensureDir(packagePath); // Create subdirectories await this.ensureDir(join(packagePath, 'data')); await this.ensureDir(join(packagePath, 'metadata')); // Copy source files and metadata const refIds = []; const manifestEntries = []; for (const source of sources) { refIds.push(source.refId); // Copy PDF to package const destPath = join(packagePath, 'data', `${source.refId}.pdf`); await fs.copyFile(source.filePath, destPath); // Create metadata file const metadataPath = join(packagePath, 'metadata', `${source.refId}.json`); await fs.writeFile(metadataPath, JSON.stringify({ paper: source.paper, refId: source.refId, acquiredAt: source.acquiredAt, originalChecksum: source.checksum, }, null, 2), 'utf-8'); manifestEntries.push({ refId: source.refId, originalPath: source.filePath, packagePath: destPath, checksum: source.checksum, sizeBytes: source.sizeBytes, }); } // Create manifest const manifestPath = join(packagePath, 'manifest.json'); const manifest = { packageId: id, packageType: type, formatVersion: this.formatVersion, createdAt: new Date().toISOString(), sourceCount: sources.length, entries: manifestEntries, }; await fs.writeFile(manifestPath, JSON.stringify(manifest, null, 2), 'utf-8'); // Calculate total size let totalSize = 0; for (const entry of manifestEntries) { totalSize += entry.sizeBytes; } // Compute package checksum const packageChecksum = await this.computeDirectoryChecksum(packagePath); const archivePackage = { type, id, path: packagePath, createdAt: new Date().toISOString(), sources: refIds, manifestPath, sizeBytes: totalSize, packageChecksum, }; return archivePackage; } /** * Verify integrity of archive package */ async verifyIntegrity(packagePath) { const manifestPath = join(packagePath, 'manifest.json'); // Load manifest let manifest; try { const data = await fs.readFile(manifestPath, 'utf-8'); manifest = JSON.parse(data); } catch (error) { return { valid: false, verifiedAt: new Date().toISOString(), files: [], missingFiles: [], extraFiles: [], summary: 'Failed to load manifest', }; } const fileResults = []; const missingFiles = []; const expectedFiles = new Set(); // Verify each file in manifest for (const entry of manifest.entries) { expectedFiles.add(entry.packagePath); try { const actualChecksum = await this.computeChecksum(entry.packagePath); const valid = actualChecksum === entry.checksum; fileResults.push({ path: entry.packagePath, expectedChecksum: entry.checksum, actualChecksum, valid, }); } catch (error) { missingFiles.push(entry.packagePath); fileResults.push({ path: entry.packagePath, expectedChecksum: entry.checksum, actualChecksum: '', valid: false, }); } } // Check for extra files const actualFiles = await this.listFilesRecursive(join(packagePath, 'data')); const extraFiles = actualFiles.filter((f) => !expectedFiles.has(f)); const allValid = fileResults.every((f) => f.valid) && missingFiles.length === 0 && extraFiles.length === 0; const summary = allValid ? 'Package integrity verified' : `Integrity check failed: ${fileResults.filter((f) => !f.valid).length} invalid, ${missingFiles.length} missing, ${extraFiles.length} extra`; return { valid: allValid, verifiedAt: new Date().toISOString(), files: fileResults, missingFiles, extraFiles, summary, }; } /** * Export reproducibility package for workflow */ async exportReproducibilityPackage(workflowId) { const exportPath = join(this.archiveDir, 'reproducibility', `${workflowId}-${Date.now()}.zip`); await this.ensureDir(dirname(exportPath)); // Create a simple marker file (full implementation would create ZIP) await fs.writeFile(exportPath.replace('.zip', '.txt'), `Reproducibility package for workflow ${workflowId}\nCreated: ${new Date().toISOString()}`, 'utf-8'); return exportPath; } /** * Compute checksum of file */ async computeChecksum(filePath) { const content = await fs.readFile(filePath); const hash = createHash('sha256'); hash.update(content); return hash.digest('hex'); } /** * Compute checksum of directory contents */ async computeDirectoryChecksum(dirPath) { const files = await this.listFilesRecursive(dirPath); files.sort(); // Ensure consistent ordering const hash = createHash('sha256'); for (const file of files) { const content = await fs.readFile(file); hash.update(content); } return hash.digest('hex'); } /** * List all files recursively */ async listFilesRecursive(dirPath) { const files = []; try { const entries = await fs.readdir(dirPath, { withFileTypes: true }); for (const entry of entries) { const fullPath = join(dirPath, entry.name); if (entry.isDirectory()) { const subFiles = await this.listFilesRecursive(fullPath); files.push(...subFiles); } else { files.push(fullPath); } } } catch (error) { // Directory doesn't exist } return files; } /** * Generate package ID */ generatePackageId(type) { const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); return `${type.toLowerCase()}-${timestamp}`; } /** * Ensure directory exists */ async ensureDir(dir) { try { await fs.mkdir(dir, { recursive: true }); } catch (error) { if (error.code !== 'EEXIST') { throw error; } } } } //# sourceMappingURL=archival.js.map