UNPKG

prompt-version-manager

Version:

Centralized prompt management system for Human Behavior AI agents

460 lines 17.8 kB
"use strict"; /** * Git-like branch and checkout operations for PVM TypeScript. */ 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 () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.BranchManager = void 0; const fs = __importStar(require("fs/promises")); const path = __importStar(require("path")); const exceptions_1 = require("../core/exceptions"); const engine_1 = require("../storage/engine"); const objects_1 = require("../storage/objects"); const merge_1 = require("./merge"); class BranchManager { storage; repoPath; constructor(repoPath = '.pvm') { this.repoPath = repoPath; this.storage = new engine_1.StorageEngine(repoPath); } /** * Create a new branch. */ async createBranch(branchName, startPoint, force = false) { if (!(await this.isRepo())) { throw new exceptions_1.RepoNotFoundError("Not a PVM repository. Run 'pvm init' first."); } // Validate branch name if (!this.isValidBranchName(branchName)) { throw new exceptions_1.InvalidBranchNameError(`Invalid branch name: ${branchName}`); } // Check if branch already exists const branchRef = `refs/heads/${branchName}`; if (!force && await this.storage.refExists(branchRef)) { throw new exceptions_1.StorageError(`Branch '${branchName}' already exists. Use --force to overwrite.`); } let commitHash; // Determine start point if (startPoint) { // Could be a commit hash or branch name try { // Try as commit hash first if (await this.storage.objectExists(startPoint)) { commitHash = startPoint; } else { // Try as branch name const ref = startPoint.startsWith('refs/heads/') ? startPoint : `refs/heads/${startPoint}`; commitHash = await this.storage.loadRef(ref); } } catch (error) { throw new exceptions_1.StorageError(`Invalid start point: ${startPoint}`); } } else { // Default to current HEAD try { const headRef = await this.storage.loadRef('HEAD'); if (headRef.startsWith('refs/')) { // HEAD points to a branch ref, resolve it to get the commit hash commitHash = await this.storage.loadRef(headRef); } else { // HEAD points directly to a commit commitHash = headRef; } } catch (error) { throw new exceptions_1.StorageError('No commits found. Cannot create branch.'); } } // Verify the commit exists if (!(await this.storage.objectExists(commitHash))) { throw new exceptions_1.StorageError(`Commit ${commitHash} not found`); } // Create the branch reference await this.storage.storeRef(branchRef, commitHash, 'branch'); return commitHash; } /** * Delete a branch. */ async deleteBranch(branchName, force = false) { if (!(await this.isRepo())) { throw new exceptions_1.RepoNotFoundError("Not a PVM repository. Run 'pvm init' first."); } const branchRef = `refs/heads/${branchName}`; // Check if branch exists if (!(await this.storage.refExists(branchRef))) { throw new exceptions_1.BranchNotFoundError(`Branch '${branchName}' not found`); } // Check if it's the current branch const currentBranch = await this.getCurrentBranch(); if (currentBranch === branchName && !force) { throw new exceptions_1.StorageError(`Cannot delete current branch '${branchName}'. Checkout another branch first or use --force.`); } // Delete the branch reference await this.storage.deleteRef(branchRef); } /** * List all branches. */ async listBranches() { if (!(await this.isRepo())) { throw new exceptions_1.RepoNotFoundError("Not a PVM repository. Run 'pvm init' first."); } const branches = {}; const refs = await this.storage.listRefs('branch'); for (const ref of refs) { if (ref.name.startsWith('refs/heads/')) { const branchName = ref.name.substring(11); // Remove "refs/heads/" prefix branches[branchName] = ref.hash; } } return branches; } /** * Get the name of the current branch. */ async getCurrentBranch() { if (!(await this.isRepo())) { return null; } try { // In Git, HEAD can point to a branch ref or directly to a commit const headRef = await this.storage.loadRef('HEAD'); if (headRef.startsWith('refs/heads/')) { // HEAD points to a branch ref return headRef.substring(11); // Remove "refs/heads/" prefix } else { // HEAD points directly to a commit (detached HEAD) // In Git, this always means we're not on any branch return null; } } catch (error) { return null; } } /** * Checkout a branch or commit. */ async checkout(target, create = false, force = false) { if (!(await this.isRepo())) { throw new exceptions_1.RepoNotFoundError("Not a PVM repository. Run 'pvm init' first."); } // Check for uncommitted changes (staging area) if (!force && await this.hasUncommittedChanges()) { throw new exceptions_1.UncommittedChangesError('You have uncommitted changes. Commit them or use --force to discard.'); } let commitHash; let branchName = null; // Determine what we're checking out // Check if target looks like a commit hash and exists as an object if (target.length === 64 && /^[0-9a-f]+$/i.test(target)) { // This looks like a commit hash - check if it exists if (await this.storage.objectExists(target)) { commitHash = target; // branchName remains null for detached HEAD } else { throw new exceptions_1.StorageError(`Commit ${target} not found`); } } else { // Assume it's a branch name const branchRef = `refs/heads/${target}`; if (await this.storage.refExists(branchRef)) { // Branch exists commitHash = await this.storage.loadRef(branchRef); branchName = target; } else if (create) { // Create new branch from current HEAD const currentHeadRef = await this.storage.loadRef('HEAD'); if (currentHeadRef.startsWith('refs/')) { // HEAD points to a branch ref, resolve it commitHash = await this.storage.loadRef(currentHeadRef); } else { // HEAD points directly to a commit commitHash = currentHeadRef; } await this.storage.storeRef(branchRef, commitHash, 'branch'); branchName = target; } else { throw new exceptions_1.BranchNotFoundError(`Branch '${target}' not found. Use -b to create it.`); } } // Verify commit exists if (!(await this.storage.objectExists(commitHash))) { throw new exceptions_1.StorageError(`Commit ${commitHash} not found`); } // Update HEAD appropriately if (branchName) { // Checking out a branch - HEAD should point to the branch ref await this.storage.storeRef('HEAD', `refs/heads/${branchName}`); } else { // Checking out a commit directly - HEAD points to commit hash await this.storage.storeRef('HEAD', commitHash, 'head'); } // Clear staging area (like git checkout does) await this.clearStaging(); return commitHash; } /** * Merge a branch into the current branch. */ async merge(sourceBranch, message, noFf = false, author = 'PVM User', strategy = 'recursive') { if (!(await this.isRepo())) { throw new exceptions_1.RepoNotFoundError("Not a PVM repository. Run 'pvm init' first."); } // Check for uncommitted changes if (await this.hasUncommittedChanges()) { throw new exceptions_1.UncommittedChangesError('You have uncommitted changes. Commit them first.'); } // Get current branch const currentBranch = await this.getCurrentBranch(); if (!currentBranch) { throw new exceptions_1.StorageError('Cannot merge while in detached HEAD state'); } // Get source branch commit const sourceRef = `refs/heads/${sourceBranch}`; if (!(await this.storage.refExists(sourceRef))) { throw new exceptions_1.BranchNotFoundError(`Branch '${sourceBranch}' not found`); } const sourceCommitHash = await this.storage.loadRef(sourceRef); // Resolve HEAD to actual commit hash const headRef = await this.storage.loadRef('HEAD'); let currentCommitHash; if (headRef.startsWith('refs/')) { currentCommitHash = await this.storage.loadRef(headRef); } else { currentCommitHash = headRef; } // Check if it's a fast-forward merge if (!noFf && await this.isAncestor(currentCommitHash, sourceCommitHash)) { // Fast-forward merge - update both branch and HEAD to point to new commit await this.storage.storeRef(`refs/heads/${currentBranch}`, sourceCommitHash, 'branch'); await this.storage.storeRef('HEAD', sourceCommitHash, 'head'); return null; } // Perform the actual merge const mergeResult = await (0, merge_1.mergeBranches)(this.storage, currentCommitHash, sourceCommitHash, strategy); if (mergeResult.hasConflicts) { // Report conflicts const conflictFiles = mergeResult.conflicts.map(c => c.filename); throw new exceptions_1.MergeConflictError(`Merge conflict in files: ${conflictFiles.join(', ')}\n` + `Fix conflicts and commit the result.`); } // Create merge commit if (!message) { message = `Merge branch '${sourceBranch}' into ${currentBranch}`; } // Create merge commit with both parents const mergeCommit = new objects_1.CommitObject({ hash: '', treeHash: mergeResult.mergedTreeHash, parentHashes: [currentCommitHash, sourceCommitHash], author: author, message: message, timestamp: new Date(), promptVersions: [], // Will be loaded from tree metadata: {} }); const mergeCommitHash = await this.storage.storeObject(mergeCommit); // Update current branch to point to merge commit await this.storage.storeRef(`refs/heads/${currentBranch}`, mergeCommitHash, 'branch'); return mergeCommitHash; } /** * Get detailed information about a branch. */ async getBranchInfo(branchName) { if (!(await this.isRepo())) { throw new exceptions_1.RepoNotFoundError("Not a PVM repository. Run 'pvm init' first."); } const branchRef = `refs/heads/${branchName}`; if (!(await this.storage.refExists(branchRef))) { throw new exceptions_1.BranchNotFoundError(`Branch '${branchName}' not found`); } const commitHash = await this.storage.loadRef(branchRef); try { const commitObj = await this.storage.loadObject(commitHash); commitObj.commit.hash = commitHash; // Set hash from storage key return { name: branchName, commitHash, commitMessage: commitObj.commit.message, commitAuthor: commitObj.commit.author, commitTimestamp: commitObj.commit.timestamp, isCurrent: await this.getCurrentBranch() === branchName, }; } catch (error) { return { name: branchName, commitHash, isCurrent: await this.getCurrentBranch() === branchName, }; } } /** * Check if current directory is a PVM repository. */ async isRepo() { try { await fs.access(this.repoPath); await fs.access(path.join(this.repoPath, 'objects')); await fs.access(path.join(this.repoPath, 'refs')); // Also check if HEAD exists (indicating proper initialization) try { await this.storage.loadRef('HEAD'); return true; } catch { return false; } } catch { return false; } } /** * Validate branch name according to Git rules. */ isValidBranchName(name) { if (!name || ['HEAD', 'ORIG_HEAD', 'FETCH_HEAD', 'MERGE_HEAD'].includes(name)) { return false; } // Basic validation - more comprehensive rules can be added const invalidChars = [' ', '~', '^', ':', '?', '*', '[', '\\']; for (const char of invalidChars) { if (name.includes(char)) { return false; } } if (name.includes('..') || name.includes('@{')) { return false; } if (name.startsWith('/') || name.endsWith('/') || name.endsWith('.')) { return false; } if (name.includes('//') || name.startsWith('-')) { return false; } return true; } /** * Get all staged prompts (Python-compatible approach). */ async getStagedPrompts() { const refs = await this.storage.listRefs(); const staged = []; for (const ref of refs) { if (ref.name.startsWith('staging/')) { const tag = ref.name.substring(8).split('_')[0]; // Extract tag from ref name staged.push([ref.hash, tag]); } } return staged; } /** * Clear the staging area (Python-compatible approach). */ async clearStaging() { const refs = await this.storage.listRefs(); for (const ref of refs) { if (ref.name.startsWith('staging/')) { await this.storage.deleteRef(ref.name); } } } /** * Check if there are uncommitted changes in the staging area. */ async hasUncommittedChanges() { try { const stagedPrompts = await this.getStagedPrompts(); return stagedPrompts.length > 0; } catch (error) { return false; } } /** * Check if ancestor_hash is an ancestor of descendant_hash. */ async isAncestor(ancestorHash, descendantHash) { // Simplified implementation - walk commit history const visited = new Set(); let current = descendantHash; while (current && !visited.has(current)) { if (current === ancestorHash) { return true; } visited.add(current); try { const commitObj = await this.storage.loadObject(current); if (commitObj.commit.parentHashes.length > 0) { current = commitObj.commit.parentHashes[0]; // Follow first parent } else { break; } } catch (error) { break; } } return false; } /** * Close the branch manager and underlying storage. */ async close() { await this.storage.close(); } } exports.BranchManager = BranchManager; //# sourceMappingURL=branches.js.map