UNPKG

agentic-qe

Version:

Agentic Quality Engineering Fleet System - AI-driven quality management platform

347 lines 12.4 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.ArtifactWorkflow = void 0; const crypto = __importStar(require("crypto")); const fs = __importStar(require("fs")); const path = __importStar(require("path")); const uuid_1 = require("uuid"); /** * ArtifactWorkflow - Artifact-Centric Design with Manifest Storage * * Implements Claude Flow's artifact-centric pattern: * - Large outputs (code, docs, data) stored as files * - Small manifests stored in memory (artifacts table) * - SHA256 integrity verification * - Tag-based organization * - Version history tracking * - Reference by ID, not content * * Based on AQE-IMPROVEMENT-PLAN.md Phase 1 */ class ArtifactWorkflow { constructor(memory, artifactsDir = '.aqe/artifacts') { this.memory = memory; this.artifactsDir = artifactsDir; // Ensure artifacts directory exists if (!fs.existsSync(this.artifactsDir)) { fs.mkdirSync(this.artifactsDir, { recursive: true }); } } /** * Create a new artifact with content storage and manifest * * @param content - Artifact content (code, doc, data, config) * @param options - Artifact metadata (kind, path, tags) * @returns Artifact ID for reference */ async createArtifact(content, options) { // Validate inputs if (!content) { throw new Error('Artifact content cannot be empty'); } if (!this.isValidKind(options.kind)) { throw new Error(`Invalid artifact kind: ${options.kind}`); } if (!options.path) { throw new Error('Artifact path is required'); } // Generate unique artifact ID const artifactId = `artifact:${(0, uuid_1.v4)()}`; // Compute SHA256 hash for integrity const sha256 = crypto.createHash('sha256').update(content).digest('hex'); // Store artifact content to file const filePath = path.join(this.artifactsDir, options.path); const fileDir = path.dirname(filePath); // Create nested directories if needed if (!fs.existsSync(fileDir)) { fs.mkdirSync(fileDir, { recursive: true }); } fs.writeFileSync(filePath, content, 'utf-8'); // Create manifest (small metadata) const manifest = { id: artifactId, kind: options.kind, path: options.path, sha256: sha256, tags: options.tags, size: content.length, createdAt: Date.now() }; // Store manifest in artifacts table (TTL 0 - never expires) await this.memory.store(artifactId, manifest, { partition: 'artifacts', ttl: 0 }); return artifactId; } /** * Retrieve artifact by ID with integrity verification * * @param artifactId - Artifact ID to retrieve * @returns Artifact manifest and content */ async retrieveArtifact(artifactId) { // Validate artifact ID format if (!artifactId.startsWith('artifact:')) { throw new Error(`Invalid artifact ID format: ${artifactId}`); } // Retrieve manifest from memory const manifestEntry = await this.memory.retrieve(artifactId, { partition: 'artifacts' }); const manifest = manifestEntry.value; // Read artifact content from file const filePath = path.join(this.artifactsDir, manifest.path); if (!fs.existsSync(filePath)) { throw new Error(`Artifact file not found: ${filePath}`); } const content = fs.readFileSync(filePath, 'utf-8'); // Verify SHA256 integrity const computedHash = crypto.createHash('sha256').update(content).digest('hex'); if (computedHash !== manifest.sha256) { throw new Error(`Artifact integrity check failed for ${artifactId}`); } return { id: artifactId, manifest, content }; } /** * Query artifacts by tags (AND logic - all tags must match) * * @param tags - Tags to filter by * @returns Array of matching artifacts */ async queryByTags(tags) { // Query all artifacts const allArtifacts = await this.memory.query('artifact:*', { partition: 'artifacts' }); // Filter by tags (AND logic) and limit results const results = allArtifacts .filter((entry) => { const manifest = entry.value; return tags.every(tag => manifest.tags.includes(tag)); }) .slice(0, 1000); return results.map((entry) => ({ id: entry.key, manifest: entry.value })); } /** * Query artifacts by kind * * @param kind - Artifact kind to filter by * @returns Array of matching artifacts */ async queryByKind(kind) { const allArtifacts = await this.memory.query('artifact:*', { partition: 'artifacts' }); const results = allArtifacts .filter((entry) => { const manifest = entry.value; return manifest.kind === kind; }) .slice(0, 1000); return results.map((entry) => ({ id: entry.key, manifest: entry.value })); } /** * Query artifacts by kind AND tags * * @param kind - Artifact kind * @param tags - Tags to filter by * @returns Array of matching artifacts */ async queryByKindAndTags(kind, tags) { const allArtifacts = await this.memory.query('artifact:*', { partition: 'artifacts' }); const results = allArtifacts .filter((entry) => { const manifest = entry.value; return (manifest.kind === kind && tags.every(tag => manifest.tags.includes(tag))); }) .slice(0, 1000); return results.map((entry) => ({ id: entry.key, manifest: entry.value })); } /** * Create a new version of an existing artifact * * @param previousArtifactId - ID of the previous version * @param content - New content * @param options - Version options (path, tags) * @returns New artifact ID */ async createArtifactVersion(previousArtifactId, content, options) { // Retrieve previous version to inherit metadata const previousEntry = await this.memory.retrieve(previousArtifactId, { partition: 'artifacts' }); const previousManifest = previousEntry.value; // Generate unique artifact ID first const newArtifactId = `artifact:${(0, uuid_1.v4)()}`; // If path not provided, create versioned path to avoid overwrites let newPath = options.path; if (!newPath) { const pathParts = path.parse(previousManifest.path); const timestamp = Date.now(); newPath = path.join(pathParts.dir, `${pathParts.name}.v${timestamp}${pathParts.ext}`); } // Compute SHA256 hash for integrity const sha256 = crypto.createHash('sha256').update(content).digest('hex'); // Store artifact content to file const filePath = path.join(this.artifactsDir, newPath); const fileDir = path.dirname(filePath); // Create nested directories if needed if (!fs.existsSync(fileDir)) { fs.mkdirSync(fileDir, { recursive: true }); } fs.writeFileSync(filePath, content, 'utf-8'); // Create new manifest with version link const newManifest = { id: newArtifactId, kind: previousManifest.kind, path: newPath, sha256: sha256, tags: options.tags, size: content.length, createdAt: Date.now(), previousVersion: previousArtifactId }; // Store manifest in artifacts table await this.memory.store(newArtifactId, newManifest, { partition: 'artifacts', ttl: 0 }); return newArtifactId; } /** * Get version history for an artifact * * @param artifactId - Artifact ID * @returns Array of artifacts in version chain (newest first) */ async getVersionHistory(artifactId) { const history = []; let currentId = artifactId; while (currentId) { const artifact = await this.retrieveArtifact(currentId); history.push(artifact); currentId = artifact.manifest.previousVersion; } return history; } /** * Get the latest version of an artifact * * @param artifactId - Any artifact ID in the version chain * @returns Latest version */ async getLatestVersion(artifactId) { // Get all artifacts and find ones pointing to this artifact const allArtifacts = await this.memory.query('artifact:*', { partition: 'artifacts' }); // Build version graph const versionMap = new Map(); // previousId -> currentId allArtifacts.slice(0, 1000).forEach((entry) => { const manifest = entry.value; if (manifest.previousVersion) { versionMap.set(manifest.previousVersion, manifest.id); } }); // Follow chain to find latest let latestId = artifactId; while (versionMap.has(latestId)) { latestId = versionMap.get(latestId); } return this.retrieveArtifact(latestId); } /** * List all artifacts * * @param options - Query options (limit) * @returns Array of all artifacts */ async listArtifacts(options) { const allArtifacts = await this.memory.query('artifact:*', { partition: 'artifacts' }); const limitedResults = allArtifacts.slice(0, options?.limit || 1000); return limitedResults.map((entry) => ({ id: entry.key, manifest: entry.value })); } /** * Delete an artifact and its file * * @param artifactId - Artifact ID to delete */ async deleteArtifact(artifactId) { // Retrieve manifest to get file path const manifestEntry = await this.memory.retrieve(artifactId, { partition: 'artifacts' }); const manifest = manifestEntry.value; // Delete file const filePath = path.join(this.artifactsDir, manifest.path); if (fs.existsSync(filePath)) { fs.unlinkSync(filePath); } // Delete manifest from database await this.memory.delete(artifactId, 'artifacts'); } /** * Delete an artifact and all its versions * * @param artifactId - Artifact ID (any version in chain) */ async deleteArtifactWithVersions(artifactId) { const history = await this.getVersionHistory(artifactId); for (const artifact of history) { await this.deleteArtifact(artifact.id); } } /** * Validate artifact kind */ isValidKind(kind) { return ['code', 'doc', 'data', 'config'].includes(kind); } } exports.ArtifactWorkflow = ArtifactWorkflow; //# sourceMappingURL=ArtifactWorkflow.js.map