prompt-version-manager
Version:
Centralized prompt management system for Human Behavior AI agents
460 lines • 17.8 kB
JavaScript
"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