agentic-qe
Version:
Agentic Quality Engineering Fleet System - AI-driven quality management platform
347 lines • 12.4 kB
JavaScript
;
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