UNPKG

trellis

Version:

Agentic State Engine — event-sourced causal graph with branching, decision traces, and realtime sync for AI-native applications

1,602 lines (1,595 loc) 110 kB
import { init_identity, signMessage, verifySignature } from "./chunk-KFJMKL4Y.js"; import { init_test_manifest, loadTestManifest, resolveIssueStartCriteria } from "./chunk-PBH357QR.js"; import { ISSUE_TYPES, branchEntityId, branchHeadEntity, criterionEntityId, decisionEntityId, dirEntityId, fileEntityId, init_types, issueEntityId, laneEntityId, writerPrincipal } from "./chunk-E2CFJKLU.js"; import { createVcsOp, hashVcsOp, init_ops } from "./chunk-GRWQPKYK.js"; import { __esm } from "./chunk-2ESYSVXG.js"; // src/vcs/decompose.ts function zoneOwnerPrincipal(zoneId) { const m = /^turtle:\/\/(.+?)\/zone\/.+$/.exec(zoneId); return m ? `identity:${m[1]}` : null; } function dirname(p) { const i = p.lastIndexOf("/"); return i <= 0 ? "." : p.slice(0, i); } function pickFacts(input) { if (!Array.isArray(input)) return []; return input.filter((item) => { const fact = item; return typeof fact.e === "string" && typeof fact.a === "string" && (typeof fact.v === "string" || typeof fact.v === "number" || typeof fact.v === "boolean"); }); } function pickLinks(input) { if (!Array.isArray(input)) return []; return input.filter((item) => { const link = item; return typeof link.e1 === "string" && typeof link.a === "string" && typeof link.e2 === "string"; }); } function claimRetractionFacts(entityId, vcs) { const facts = []; if (vcs.claimedLaneId) { facts.push({ e: entityId, a: "claimedLaneId", v: vcs.claimedLaneId }); } if (vcs.claimedSessionId) { facts.push({ e: entityId, a: "claimedSessionId", v: vcs.claimedSessionId }); } if (vcs.claimedAt) { facts.push({ e: entityId, a: "claimedAt", v: vcs.claimedAt }); } return facts; } function decompose(op) { const result = { addFacts: [], addLinks: [], deleteFacts: [], deleteLinks: [] }; const vcs = op.vcs; if (!vcs) return result; if (vcs.issueId && vcs.oldIssueStatus) { result.deleteFacts.push({ e: issueEntityId(vcs.issueId), a: "status", v: vcs.oldIssueStatus }); } switch (op.kind) { case "vcs:fileAdd": { if (!vcs.filePath) break; const eid = fileEntityId(vcs.filePath); const dir = dirname(vcs.filePath); const did = dirEntityId(dir === "." ? "" : dir); result.addFacts.push( { e: eid, a: "type", v: "FileNode" }, { e: eid, a: "path", v: vcs.filePath } ); if (vcs.contentHash) { result.addFacts.push({ e: eid, a: "contentHash", v: vcs.contentHash }); } if (vcs.size !== void 0) { result.addFacts.push({ e: eid, a: "size", v: vcs.size }); } if (vcs.language) { result.addFacts.push({ e: eid, a: "language", v: vcs.language }); } result.addFacts.push({ e: eid, a: "lastModified", v: op.timestamp }); result.addFacts.push( { e: did, a: "type", v: "DirectoryNode" }, { e: did, a: "path", v: dir === "." ? "" : dir } ); result.addLinks.push({ e1: did, a: "contains", e2: eid }); break; } case "vcs:fileModify": { if (!vcs.filePath) break; const eid = fileEntityId(vcs.filePath); if (vcs.oldContentHash) { result.deleteFacts.push({ e: eid, a: "contentHash", v: vcs.oldContentHash }); } if (vcs.contentHash) { result.addFacts.push({ e: eid, a: "contentHash", v: vcs.contentHash }); } if (vcs.size !== void 0) { result.addFacts.push({ e: eid, a: "size", v: vcs.size }); } result.addFacts.push({ e: eid, a: "lastModified", v: op.timestamp }); break; } case "vcs:fileDelete": { if (!vcs.filePath) break; const eid = fileEntityId(vcs.filePath); const dir = dirname(vcs.filePath); const did = dirEntityId(dir === "." ? "" : dir); result.deleteFacts.push( { e: eid, a: "type", v: "FileNode" }, { e: eid, a: "path", v: vcs.filePath } ); if (vcs.contentHash) { result.deleteFacts.push({ e: eid, a: "contentHash", v: vcs.contentHash }); } result.deleteLinks.push({ e1: did, a: "contains", e2: eid }); break; } case "vcs:fileRename": { if (!vcs.filePath || !vcs.oldFilePath) break; const eid = fileEntityId(vcs.oldFilePath); const oldDir = dirname(vcs.oldFilePath); const newDir = dirname(vcs.filePath); const oldDid = dirEntityId(oldDir === "." ? "" : oldDir); const newDid = dirEntityId(newDir === "." ? "" : newDir); result.deleteFacts.push({ e: eid, a: "path", v: vcs.oldFilePath }); result.addFacts.push({ e: eid, a: "path", v: vcs.filePath }); result.addFacts.push({ e: eid, a: "lastModified", v: op.timestamp }); result.deleteLinks.push({ e1: oldDid, a: "contains", e2: eid }); result.addFacts.push( { e: newDid, a: "type", v: "DirectoryNode" }, { e: newDid, a: "path", v: newDir === "." ? "" : newDir } ); result.addLinks.push({ e1: newDid, a: "contains", e2: eid }); break; } case "vcs:branchCreate": { if (!vcs.branchName) break; const bid = `branch:${vcs.branchName}`; result.addFacts.push( { e: bid, a: "type", v: "Branch" }, { e: bid, a: "name", v: vcs.branchName }, { e: bid, a: "createdAt", v: op.timestamp }, { e: bid, a: "createdBy", v: op.agentId } ); if (vcs.targetOpHash) { result.addFacts.push({ e: bid, a: "headOpHash", v: vcs.targetOpHash }); } if (vcs.baseBranch) { result.addLinks.push({ e1: bid, a: "forkedFrom", e2: `branch:${vcs.baseBranch}` }); } break; } case "vcs:branchDelete": { if (!vcs.branchName) break; const bid = `branch:${vcs.branchName}`; result.deleteFacts.push( { e: bid, a: "type", v: "Branch" }, { e: bid, a: "name", v: vcs.branchName } ); break; } case "vcs:branchAdvance": { if (!vcs.branchName || !vcs.targetOpHash) break; const wid = writerPrincipal(op); const bid = branchHeadEntity(vcs.branchName, wid); result.addFacts.push({ e: bid, a: "headOpHash", v: vcs.targetOpHash }); break; } case "vcs:milestoneCreate": { if (!vcs.milestoneId) break; const mid = vcs.milestoneId; result.addFacts.push( { e: mid, a: "type", v: "Milestone" }, { e: mid, a: "createdAt", v: op.timestamp }, { e: mid, a: "createdBy", v: op.agentId } ); if (vcs.message) { result.addFacts.push({ e: mid, a: "message", v: vcs.message }); } if (vcs.fromOpHash) { result.addFacts.push({ e: mid, a: "fromOpHash", v: vcs.fromOpHash }); } if (vcs.toOpHash) { result.addFacts.push({ e: mid, a: "toOpHash", v: vcs.toOpHash }); } break; } case "vcs:checkpointCreate": { const cid = `checkpoint:${op.hash}`; result.addFacts.push( { e: cid, a: "type", v: "Checkpoint" }, { e: cid, a: "createdAt", v: op.timestamp }, { e: cid, a: "atOpHash", v: op.hash } ); if (vcs.trigger) { result.addFacts.push({ e: cid, a: "trigger", v: vcs.trigger }); } break; } // ----- Issue tracking ----- case "vcs:issueCreate": { if (!vcs.issueId) break; const eid = issueEntityId(vcs.issueId); result.addFacts.push( { e: eid, a: "type", v: "Issue" }, // ADR 0026: `type` is the ENTITY type (Issue); `issueType` is what kind // of issue it is (epic/issue/spike/msg). Absent ⇒ 'issue'. { e: eid, a: "issueType", v: vcs.issueType ?? "issue" }, { e: eid, a: "status", v: vcs.issueStatus ?? "backlog" }, { e: eid, a: "createdAt", v: op.timestamp }, { e: eid, a: "createdBy", v: op.agentId } ); if (vcs.issueTitle) { result.addFacts.push({ e: eid, a: "title", v: vcs.issueTitle }); } if (vcs.issueDescription) { result.addFacts.push({ e: eid, a: "description", v: vcs.issueDescription }); } if (vcs.issuePriority) { result.addFacts.push({ e: eid, a: "priority", v: vcs.issuePriority }); } if (vcs.issueLabels && vcs.issueLabels.length > 0) { result.addFacts.push({ e: eid, a: "labels", v: vcs.issueLabels.join(",") }); } if (vcs.issueAssignee) { result.addFacts.push({ e: eid, a: "assignee", v: vcs.issueAssignee }); } if (vcs.parentIssueId) { result.addLinks.push({ e1: eid, a: "childOf", e2: issueEntityId(vcs.parentIssueId) }); } break; } case "vcs:issueUpdate": { if (!vcs.issueId) break; const eid = issueEntityId(vcs.issueId); if (vcs.issueType) { for (const prior of ISSUE_TYPES) { result.deleteFacts.push({ e: eid, a: "issueType", v: prior }); } result.addFacts.push({ e: eid, a: "issueType", v: vcs.issueType }); } if (vcs.issueStatus) { result.addFacts.push({ e: eid, a: "status", v: vcs.issueStatus }); } if (vcs.issuePriority) { result.addFacts.push({ e: eid, a: "priority", v: vcs.issuePriority }); } if (vcs.issueLabels) { result.addFacts.push({ e: eid, a: "labels", v: vcs.issueLabels.join(",") }); } if (vcs.issueTitle) { result.addFacts.push({ e: eid, a: "title", v: vcs.issueTitle }); } if (vcs.issueAssignee) { result.addFacts.push({ e: eid, a: "assignee", v: vcs.issueAssignee }); } if (vcs.issueDescription !== void 0) { result.addFacts.push({ e: eid, a: "description", v: vcs.issueDescription }); } if (vcs.oldParentIssueId) { result.deleteLinks.push({ e1: eid, a: "childOf", e2: issueEntityId(vcs.oldParentIssueId) }); } if (vcs.parentIssueId) { result.addLinks.push({ e1: eid, a: "childOf", e2: issueEntityId(vcs.parentIssueId) }); } break; } case "vcs:issueStart": { if (!vcs.issueId) break; const eid = issueEntityId(vcs.issueId); result.addFacts.push( { e: eid, a: "status", v: "in_progress" }, { e: eid, a: "startedAt", v: op.timestamp } ); if (vcs.issueAssignee) { result.addFacts.push({ e: eid, a: "assignee", v: vcs.issueAssignee }); } if (vcs.branchName) { result.addLinks.push({ e1: eid, a: "trackedOn", e2: `branch:${vcs.branchName}` }); } break; } case "vcs:issuePause": { if (!vcs.issueId) break; const eid = issueEntityId(vcs.issueId); result.addFacts.push( { e: eid, a: "status", v: "paused" }, { e: eid, a: "pausedAt", v: op.timestamp } ); if (vcs.pauseNote) { result.addFacts.push({ e: eid, a: "pauseNote", v: vcs.pauseNote }); } result.deleteFacts.push(...claimRetractionFacts(eid, vcs)); break; } case "vcs:issueResume": { if (!vcs.issueId) break; const eid = issueEntityId(vcs.issueId); result.addFacts.push( { e: eid, a: "status", v: "in_progress" }, { e: eid, a: "resumedAt", v: op.timestamp }, { e: eid, a: "pauseNote", v: "" } ); break; } case "vcs:issueClose": { if (!vcs.issueId) break; const eid = issueEntityId(vcs.issueId); result.addFacts.push( { e: eid, a: "status", v: "closed" }, { e: eid, a: "closedAt", v: op.timestamp } ); result.deleteFacts.push(...claimRetractionFacts(eid, vcs)); break; } case "vcs:issueClaim": { if (!vcs.issueId || !vcs.claimedLaneId) break; const eid = issueEntityId(vcs.issueId); result.addFacts.push( { e: eid, a: "claimedLaneId", v: vcs.claimedLaneId }, { e: eid, a: "claimedAt", v: op.timestamp } ); if (vcs.claimedSessionId) { result.addFacts.push({ e: eid, a: "claimedSessionId", v: vcs.claimedSessionId }); } break; } case "vcs:issueClaimRelease": { if (!vcs.issueId) break; const eid = issueEntityId(vcs.issueId); result.deleteFacts.push(...claimRetractionFacts(eid, vcs)); break; } case "vcs:issueReopen": { if (!vcs.issueId) break; const eid = issueEntityId(vcs.issueId); result.addFacts.push({ e: eid, a: "status", v: "queue" }); break; } case "vcs:criterionAdd": { if (!vcs.criterionId || !vcs.issueId) break; const ceid = vcs.criterionId; result.addFacts.push( { e: ceid, a: "type", v: "Criterion" }, { e: ceid, a: "status", v: "pending" }, { e: ceid, a: "createdAt", v: op.timestamp } ); if (vcs.criterionDescription) { result.addFacts.push({ e: ceid, a: "description", v: vcs.criterionDescription }); } if (vcs.criterionCommand) { result.addFacts.push({ e: ceid, a: "command", v: vcs.criterionCommand }); } if (vcs.criterionSuite) { result.addFacts.push({ e: ceid, a: "suite", v: vcs.criterionSuite }); } result.addLinks.push({ e1: ceid, a: "criterionOf", e2: issueEntityId(vcs.issueId) }); break; } case "vcs:criterionUpdate": { if (!vcs.criterionId) break; const ceid = vcs.criterionId; if (vcs.criterionStatus) { for (const prior of ["pending", "passed", "failed"]) { result.deleteFacts.push({ e: ceid, a: "status", v: prior }); } result.addFacts.push({ e: ceid, a: "status", v: vcs.criterionStatus }); } if (vcs.criterionOutput) { result.addFacts.push({ e: ceid, a: "lastOutput", v: vcs.criterionOutput }); } result.addFacts.push({ e: ceid, a: "lastRunAt", v: op.timestamp }); break; } case "vcs:criterionRemove": { if (!vcs.criterionId) break; result.addFacts.push({ e: vcs.criterionId, a: "retracted", v: true }); break; } case "vcs:testRun": { if (!vcs.testRunId) break; const rid = vcs.testRunId; result.addFacts.push( { e: rid, a: "type", v: "TestRun" }, { e: rid, a: "createdAt", v: op.timestamp }, { e: rid, a: "createdBy", v: op.agentId } ); if (vcs.testRunStatus) { result.addFacts.push({ e: rid, a: "status", v: vcs.testRunStatus }); } if (vcs.testRunSuite) { result.addFacts.push({ e: rid, a: "suite", v: vcs.testRunSuite }); } if (vcs.testRunCommand) { result.addFacts.push({ e: rid, a: "command", v: vcs.testRunCommand }); } if (vcs.testRunOutput) { result.addFacts.push({ e: rid, a: "lastOutput", v: vcs.testRunOutput }); } if (vcs.testRunExitCode !== void 0) { result.addFacts.push({ e: rid, a: "exitCode", v: vcs.testRunExitCode }); } if (vcs.testRunDurationMs !== void 0) { result.addFacts.push({ e: rid, a: "durationMs", v: vcs.testRunDurationMs }); } if (vcs.testRunTrigger) { result.addFacts.push({ e: rid, a: "trigger", v: vcs.testRunTrigger }); } if (vcs.laneId) { result.addFacts.push({ e: rid, a: "laneId", v: vcs.laneId }); } if (vcs.issueId) { result.addLinks.push({ e1: rid, a: "testRunOf", e2: issueEntityId(vcs.issueId) }); } break; } // ----- Issue blocking ----- case "vcs:issueBlock": { if (!vcs.issueId || !vcs.blockedByIssueId) break; const eid = issueEntityId(vcs.issueId); const blockerEid = issueEntityId(vcs.blockedByIssueId); result.addLinks.push({ e1: eid, a: "blockedBy", e2: blockerEid }); break; } case "vcs:issueUnblock": { if (!vcs.issueId || !vcs.blockedByIssueId) break; const eid = issueEntityId(vcs.issueId); const blockerEid = issueEntityId(vcs.blockedByIssueId); result.deleteLinks.push({ e1: eid, a: "blockedBy", e2: blockerEid }); break; } // ----- Decision traces ----- case "vcs:decisionRecord": { if (!vcs.decisionId) break; const did = decisionEntityId(vcs.decisionId); result.addFacts.push( { e: did, a: "type", v: "Decision" }, { e: did, a: "createdAt", v: op.timestamp }, { e: did, a: "createdBy", v: op.agentId } ); if (vcs.decisionToolName) { result.addFacts.push({ e: did, a: "toolName", v: vcs.decisionToolName }); } if (vcs.decisionToolInput) { result.addFacts.push({ e: did, a: "toolInput", v: vcs.decisionToolInput }); } if (vcs.decisionToolOutput) { result.addFacts.push({ e: did, a: "outputSummary", v: vcs.decisionToolOutput }); } if (vcs.decisionContext) { result.addFacts.push({ e: did, a: "context", v: vcs.decisionContext }); } if (vcs.decisionRationale) { result.addFacts.push({ e: did, a: "rationale", v: vcs.decisionRationale }); } if (vcs.decisionAlternatives) { result.addFacts.push({ e: did, a: "alternatives", v: vcs.decisionAlternatives }); } break; } case "vcs:chatMessage": { if (!vcs.chatSessionId) break; const conv = `conversation:${vcs.chatSessionId}`; const msg = `message:${op.hash}`; result.addFacts.push( { e: conv, a: "type", v: "Conversation" }, { e: conv, a: "createdAt", v: op.timestamp }, { e: msg, a: "type", v: "ChatMessage" }, { e: msg, a: "role", v: vcs.chatRole ?? "assistant" }, { e: msg, a: "text", v: vcs.chatText ?? "" }, { e: msg, a: "createdAt", v: op.timestamp }, { e: msg, a: "createdBy", v: op.agentId } ); if (vcs.chatLaneId) { result.addFacts.push({ e: msg, a: "laneId", v: vcs.chatLaneId }); } if (vcs.chatToolName) { result.addFacts.push({ e: msg, a: "toolName", v: vcs.chatToolName }); } if (typeof vcs.chatTokens === "number") { result.addFacts.push({ e: msg, a: "tokens", v: vcs.chatTokens }); } result.addLinks.push({ e1: conv, a: "hasMessage", e2: msg }); break; } case "vcs:laneCreate": { if (!vcs.laneId) break; const lid = laneEntityId(vcs.laneId); result.addFacts.push( { e: lid, a: "type", v: "AgentLane" }, { e: lid, a: "status", v: "active" }, { e: lid, a: "createdAt", v: op.timestamp }, { e: lid, a: "createdBy", v: op.agentId } ); if (vcs.baseBranch) { result.addFacts.push({ e: lid, a: "baseBranch", v: vcs.baseBranch }); } if (vcs.baseOpHash) { result.addFacts.push({ e: lid, a: "baseOpHash", v: vcs.baseOpHash }); } if (vcs.targetBranch) { result.addFacts.push({ e: lid, a: "targetBranch", v: vcs.targetBranch }); } if (vcs.baseOpHash) { result.addFacts.push({ e: lid, a: "headOpHash", v: vcs.baseOpHash }); } if (vcs.issueId) { result.addFacts.push({ e: lid, a: "issueId", v: vcs.issueId }); } if (vcs.sessionId) { result.addFacts.push({ e: lid, a: "sessionId", v: vcs.sessionId }); } if (vcs.parentLaneId) { result.addFacts.push({ e: lid, a: "parentLaneId", v: vcs.parentLaneId }); result.addLinks.push({ e1: lid, a: "forkedFrom", e2: laneEntityId(vcs.parentLaneId) }); } if (vcs.forkKind) { result.addFacts.push({ e: lid, a: "forkKind", v: vcs.forkKind }); } if (vcs.virtualBaseOpHash) { result.addFacts.push({ e: lid, a: "virtualBaseOpHash", v: vcs.virtualBaseOpHash }); } break; } case "vcs:laneDrop": { if (!vcs.laneId) break; const lid = laneEntityId(vcs.laneId); if (vcs.laneStatus) { result.deleteFacts.push({ e: lid, a: "status", v: "active" }); result.addFacts.push({ e: lid, a: "status", v: vcs.laneStatus }); } break; } case "vcs:lanePromoteStart": { if (!vcs.laneId) break; const lid = laneEntityId(vcs.laneId); result.deleteFacts.push({ e: lid, a: "status", v: "active" }); result.addFacts.push({ e: lid, a: "status", v: "promoting" }); break; } case "vcs:laneGc": { break; } case "vcs:lanePromoteComplete": { if (!vcs.laneId) break; const lid = laneEntityId(vcs.laneId); result.deleteFacts.push({ e: lid, a: "status", v: "promoting" }); result.addFacts.push({ e: lid, a: "status", v: "promoted" }); if (vcs.targetBranch) { result.addFacts.push({ e: lid, a: "promotedToBranch", v: vcs.targetBranch }); } break; } case "vcs:lanePromoteAbort": { if (!vcs.laneId) break; const lid = laneEntityId(vcs.laneId); result.deleteFacts.push({ e: lid, a: "status", v: "promoting" }); result.addFacts.push({ e: lid, a: "status", v: "active" }); break; } // ----- EAV store (CMS / knowledge graph) ----- // ----------------------------------------------------------------------- // Zone capability model (ADR 0022) // ----------------------------------------------------------------------- case "vcs:zoneDefine": { if (!vcs.zoneId) break; const e = `zone:${vcs.zoneId}`; const owner = zoneOwnerPrincipal(vcs.zoneId); if (!owner) break; result.addFacts.push( { e, a: "type", v: "Zone" }, { e, a: "zoneId", v: vcs.zoneId }, { e, a: "alias", v: vcs.zoneAlias ?? "" }, { e, a: "defaultVisibility", v: vcs.zoneDefaultVisibility ?? 0 }, // The zone owner is Owner from creation — authority is self-describing // in the id, so this is derived rather than asserted. { e, a: `grant:${owner}`, v: 3 /* CapabilityLevel.Owner */ } ); if (vcs.zoneParent) { result.addFacts.push({ e, a: "parentZone", v: vcs.zoneParent }); } break; } case "vcs:zoneRename": { if (!vcs.zoneId || vcs.zoneAlias === void 0) break; const e = `zone:${vcs.zoneId}`; if (vcs.oldZoneAlias !== void 0) { result.deleteFacts.push({ e, a: "alias", v: vcs.oldZoneAlias }); } result.addFacts.push({ e, a: "alias", v: vcs.zoneAlias }); break; } case "vcs:grantSet": { if (!vcs.zoneId || !vcs.grantPrincipal || !vcs.grantLevel) break; const e = `zone:${vcs.zoneId}`; const a = `grant:${vcs.grantPrincipal}`; for (const prior of [1, 2, 3]) { result.deleteFacts.push({ e, a, v: prior }); } result.addFacts.push({ e, a, v: vcs.grantLevel }); break; } case "vcs:grantRetract": { if (!vcs.zoneId || !vcs.grantPrincipal) break; const e = `zone:${vcs.zoneId}`; const a = `grant:${vcs.grantPrincipal}`; for (const prior of [1, 2, 3]) { result.deleteFacts.push({ e, a, v: prior }); } break; } case "vcs:storeAssert": { result.addFacts.push(...pickFacts(vcs.facts)); break; } case "vcs:storeRetract": { result.deleteFacts.push(...pickFacts(vcs.facts)); break; } case "vcs:storeLink": { result.addLinks.push(...pickLinks(vcs.links)); break; } case "vcs:storeUnlink": { result.deleteLinks.push(...pickLinks(vcs.links)); break; } case "vcs:gitSync": { if (vcs.gitBranch) { const bid = branchEntityId(vcs.gitBranch); result.addFacts.push( { e: bid, a: "type", v: "Branch" }, { e: bid, a: "name", v: vcs.gitBranch } ); if (vcs.gitCommitHash) { result.addFacts.push({ e: bid, a: "lastGitCommit", v: vcs.gitCommitHash }); } } break; } case "vcs:repoAttest": { if (vcs.repoId) { const ledger = `ledger:${vcs.repoId}`; result.addFacts.push( { e: ledger, a: "type", v: "Ledger" }, { e: ledger, a: "repoId", v: vcs.repoId } ); if (vcs.repoOwner) { result.addFacts.push({ e: ledger, a: "owner", v: vcs.repoOwner }); } if (vcs.repoName) { result.addFacts.push({ e: ledger, a: "name", v: vcs.repoName }); } if (vcs.projectKind) { result.addFacts.push({ e: ledger, a: "kind", v: vcs.projectKind }); } if (vcs.signedBy) { result.addFacts.push({ e: ledger, a: "attestedBy", v: vcs.signedBy }); } } break; } } return result; } var init_decompose = __esm({ "src/vcs/decompose.ts"() { "use strict"; init_types(); init_types(); init_types(); } }); // src/vcs/branch.ts import { existsSync, readFileSync, writeFileSync } from "fs"; import { join } from "path"; function shouldAdvanceBranchHead(kind) { return !BRANCH_ADVANCE_SKIP_KINDS.has(kind); } function getBranchHeadOpHash(ctx, branchName, principal) { let advances = ctx.readAllOps().filter( (op) => op.kind === "vcs:branchAdvance" && op.vcs?.branchName === branchName && op.vcs?.targetOpHash ); if (principal) { advances = advances.filter( (op) => writerPrincipal(op) === principal ); } const sorted = advances.sort( (a, b) => a.timestamp.localeCompare(b.timestamp) || a.hash.localeCompare(b.hash) ); return sorted.at(-1)?.vcs?.targetOpHash; } async function createBranch(ctx, name, currentBranch) { const existing = ctx.store.getFactsByAttribute("type").filter((f) => f.v === "Branch" && f.e === `branch:${name}`); if (existing.length > 0) { throw new Error(`Branch '${name}' already exists`); } const op = await createVcsOp("vcs:branchCreate", { agentId: ctx.agentId, previousHash: ctx.getLastOp()?.hash, vcs: { branchName: name, baseBranch: currentBranch, targetOpHash: ctx.getLastOp()?.hash } }); await ctx.applyOp(op); return op; } function switchBranch(ctx, name) { const branchFacts = ctx.store.getFactsByEntity(`branch:${name}`).filter((f) => f.a === "type" && f.v === "Branch"); if (branchFacts.length === 0) { throw new Error(`Branch '${name}' does not exist`); } } function listBranches(ctx, currentBranch) { const branchFacts = ctx.store.getFactsByAttribute("type").filter((f) => f.v === "Branch"); return branchFacts.map((f) => { const nameFact = ctx.store.getFactsByEntity(f.e).find((ef) => ef.a === "name"); const createdFact = ctx.store.getFactsByEntity(f.e).find((ef) => ef.a === "createdAt"); const name = nameFact?.v ?? f.e.replace("branch:", ""); return { name, isCurrent: name === currentBranch, createdAt: createdFact?.v }; }); } async function deleteBranch(ctx, name, currentBranch) { if (name === currentBranch) { throw new Error(`Cannot delete the current branch '${name}'`); } const branchFacts = ctx.store.getFactsByEntity(`branch:${name}`).filter((f) => f.a === "type" && f.v === "Branch"); if (branchFacts.length === 0) { throw new Error(`Branch '${name}' does not exist`); } const op = await createVcsOp("vcs:branchDelete", { agentId: ctx.agentId, previousHash: ctx.getLastOp()?.hash, vcs: { branchName: name } }); await ctx.applyOp(op); return op; } function saveBranchState(rootPath, state) { const statePath = join(rootPath, ".trellis", "state.json"); writeFileSync(statePath, JSON.stringify(state)); } function loadBranchState(rootPath) { const statePath = join(rootPath, ".trellis", "state.json"); if (existsSync(statePath)) { try { const raw = readFileSync(statePath, "utf-8"); const state = JSON.parse(raw); if (state.currentBranch) { return { currentBranch: state.currentBranch, activeLaneId: state.activeLaneId }; } } catch { } } return { currentBranch: "main" }; } var BRANCH_ADVANCE_SKIP_KINDS; var init_branch = __esm({ "src/vcs/branch.ts"() { "use strict"; init_ops(); init_types(); BRANCH_ADVANCE_SKIP_KINDS = /* @__PURE__ */ new Set([ "vcs:branchAdvance", "vcs:branchCreate", "vcs:branchDelete", "vcs:checkpointCreate" ]); } }); // src/vcs/milestone.ts async function createMilestone(ctx, message, opts) { const ops = ctx.readAllOps(); const toOpHash = opts?.toOpHash ?? ops[ops.length - 1]?.hash; let fromOpHash = opts?.fromOpHash; if (!fromOpHash) { const milestones = ops.filter((o) => o.kind === "vcs:milestoneCreate"); if (milestones.length > 0) { const lastMilestone = milestones[milestones.length - 1]; fromOpHash = lastMilestone.vcs?.toOpHash ?? lastMilestone.hash; } else { fromOpHash = ops[0]?.hash; } } const idBase = `${message}:${Date.now()}`; const msgUint8 = new TextEncoder().encode(idBase); const hashBuffer = await crypto.subtle.digest("SHA-256", msgUint8); const hashArray = Array.from(new Uint8Array(hashBuffer)); const hashHex = hashArray.map((b) => b.toString(16).padStart(2, "0")).join(""); const milestoneId = `milestone:${hashHex.slice(0, 12)}`; const fromIdx = ops.findIndex((o) => o.hash === fromOpHash); const toIdx = ops.findIndex((o) => o.hash === toOpHash); const rangeOps = fromIdx >= 0 && toIdx >= 0 ? ops.slice(fromIdx, toIdx + 1) : ops; const affectedFiles = [ ...new Set( rangeOps.filter((o) => o.vcs?.filePath).map((o) => o.vcs.filePath) ) ]; const op = await createVcsOp("vcs:milestoneCreate", { agentId: ctx.agentId, previousHash: ctx.getLastOp()?.hash, vcs: { milestoneId, message, fromOpHash, toOpHash } }); await ctx.applyOp(op); for (const file of affectedFiles) { ctx.store.addFacts([{ e: milestoneId, a: "affectsFile", v: file }]); } return op; } function listMilestones(ctx) { const milestoneFacts = ctx.store.getFactsByAttribute("type").filter((f) => f.v === "Milestone"); return milestoneFacts.map((f) => { const facts = ctx.store.getFactsByEntity(f.e); const get = (attr) => facts.find((ef) => ef.a === attr)?.v; const affectedFiles = facts.filter((ef) => ef.a === "affectsFile").map((ef) => ef.v); return { id: f.e, message: get("message"), createdAt: get("createdAt"), createdBy: get("createdBy"), fromOpHash: get("fromOpHash"), toOpHash: get("toOpHash"), affectedFiles }; }); } var init_milestone = __esm({ "src/vcs/milestone.ts"() { "use strict"; init_ops(); } }); // src/vcs/checkpoint.ts async function createCheckpoint(ctx, trigger = "manual") { const op = await createVcsOp("vcs:checkpointCreate", { agentId: ctx.agentId, previousHash: ctx.getLastOp()?.hash, vcs: { trigger } }); await ctx.applyOp(op); return op; } function listCheckpoints(ctx) { const cpFacts = ctx.store.getFactsByAttribute("type").filter((f) => f.v === "Checkpoint"); return cpFacts.map((f) => { const facts = ctx.store.getFactsByEntity(f.e); const get = (attr) => facts.find((ef) => ef.a === attr)?.v; return { id: f.e, createdAt: get("createdAt"), trigger: get("trigger"), atOpHash: get("atOpHash") }; }); } var init_checkpoint = __esm({ "src/vcs/checkpoint.ts"() { "use strict"; init_ops(); } }); // src/vcs/diff.ts function diffFileStates(stateA, stateB, blobResolver) { const diffs = []; for (const [path, bState] of stateB) { if (bState.deleted) continue; const aState = stateA.get(path); if (!aState || aState.deleted) { diffs.push({ kind: "fileAdded", path, newContentHash: bState.contentHash }); } else if (aState.contentHash !== bState.contentHash) { const diff = { kind: "fileModified", path, oldContentHash: aState.contentHash, newContentHash: bState.contentHash }; if (blobResolver && aState.contentHash && bState.contentHash) { const oldContent = blobResolver.get(aState.contentHash); const newContent = blobResolver.get(bState.contentHash); if (oldContent && newContent) { diff.unifiedDiff = generateUnifiedDiff( path, oldContent.toString("utf-8"), newContent.toString("utf-8") ); } } diffs.push(diff); } } for (const [path, aState] of stateA) { if (aState.deleted) continue; const bState = stateB.get(path); if (!bState || bState.deleted) { diffs.push({ kind: "fileDeleted", path, oldContentHash: aState.contentHash }); } } const stats = { added: diffs.filter((d) => d.kind === "fileAdded").length, modified: diffs.filter((d) => d.kind === "fileModified").length, removed: diffs.filter((d) => d.kind === "fileDeleted").length, renamed: diffs.filter((d) => d.kind === "fileRenamed").length }; return { diffs, filesChanged: diffs.map((d) => d.path), stats }; } function buildFileStateAtOp(ops, atOpHash) { const state = /* @__PURE__ */ new Map(); for (const op of ops) { if (op.vcs?.filePath) { switch (op.kind) { case "vcs:fileAdd": case "vcs:fileModify": state.set(op.vcs.filePath, { contentHash: op.vcs.contentHash }); break; case "vcs:fileDelete": state.set(op.vcs.filePath, { deleted: true }); break; case "vcs:fileRename": if (op.vcs.oldFilePath) { state.set(op.vcs.oldFilePath, { deleted: true }); } state.set(op.vcs.filePath, { contentHash: op.vcs.contentHash }); break; } } if (atOpHash && op.hash === atOpHash) break; } return state; } function diffOpRange(ops, fromHash, toHash, blobResolver) { const stateA = buildFileStateAtOp(ops, fromHash); const stateB = buildFileStateAtOp(ops, toHash); return diffFileStates(stateA, stateB, blobResolver); } function generateUnifiedDiff(filePath, oldText, newText, contextLines = 3) { const oldLines = oldText.split("\n"); const newLines = newText.split("\n"); const edits = myersDiff(oldLines, newLines); const hunks = buildHunks(edits, contextLines); if (hunks.length === 0) return ""; const lines = [`--- a/${filePath}`, `+++ b/${filePath}`]; for (const hunk of hunks) { lines.push( `@@ -${hunk.oldStart},${hunk.oldCount} +${hunk.newStart},${hunk.newCount} @@` ); for (const edit of hunk.edits) { switch (edit.kind) { case "equal": lines.push(` ${edit.line}`); break; case "delete": lines.push(`-${edit.line}`); break; case "insert": lines.push(`+${edit.line}`); break; } } } return lines.join("\n"); } function myersDiff(oldLines, newLines) { const n = oldLines.length; const m = newLines.length; if (n === 0 && m === 0) return []; if (n === 0) return newLines.map((line) => ({ kind: "insert", line })); if (m === 0) return oldLines.map((line) => ({ kind: "delete", line })); const max = n + m; const size = 2 * max + 1; const v = new Int32Array(size); const trace = []; const off = max; outer: for (let d = 0; d <= max; d++) { trace.push(v.slice()); for (let k = -d; k <= d; k += 2) { let x2; if (k === -d || k !== d && v[k - 1 + off] < v[k + 1 + off]) { x2 = v[k + 1 + off]; } else { x2 = v[k - 1 + off] + 1; } let y2 = x2 - k; while (x2 < n && y2 < m && oldLines[x2] === newLines[y2]) { x2++; y2++; } v[k + off] = x2; if (x2 >= n && y2 >= m) break outer; } } let x = n; let y = m; const edits = []; for (let d = trace.length - 1; d >= 0; d--) { const tv = trace[d]; const k = x - y; let prevK; if (d === 0) { while (x > 0 && y > 0) { x--; y--; edits.push({ kind: "equal", line: oldLines[x] }); } break; } if (k === -d || k !== d && tv[k - 1 + off] < tv[k + 1 + off]) { prevK = k + 1; } else { prevK = k - 1; } const prevX = tv[prevK + off]; const prevY = prevX - prevK; while (x > prevX && y > prevY) { x--; y--; edits.push({ kind: "equal", line: oldLines[x] }); } if (prevK === k + 1) { y--; edits.push({ kind: "insert", line: newLines[y] }); } else { x--; edits.push({ kind: "delete", line: oldLines[x] }); } } edits.reverse(); return edits; } function buildHunks(edits, contextLines) { if (edits.length === 0) return []; const changeIndices = []; for (let i = 0; i < edits.length; i++) { if (edits[i].kind !== "equal") { changeIndices.push(i); } } if (changeIndices.length === 0) return []; const hunks = []; let hunkStart = Math.max(0, changeIndices[0] - contextLines); let hunkEnd = Math.min(edits.length - 1, changeIndices[0] + contextLines); for (let i = 1; i < changeIndices.length; i++) { const changeStart = changeIndices[i] - contextLines; const changeEnd = Math.min( edits.length - 1, changeIndices[i] + contextLines ); if (changeStart <= hunkEnd + 1) { hunkEnd = changeEnd; } else { hunks.push(createHunk(edits, hunkStart, hunkEnd)); hunkStart = changeStart; hunkEnd = changeEnd; } } hunks.push(createHunk(edits, hunkStart, hunkEnd)); return hunks; } function createHunk(edits, start, end) { const hunkEdits = edits.slice(start, end + 1); let oldLine = 1; let newLine = 1; for (let i = 0; i < start; i++) { if (edits[i].kind === "equal" || edits[i].kind === "delete") oldLine++; if (edits[i].kind === "equal" || edits[i].kind === "insert") newLine++; } let oldCount = 0; let newCount = 0; for (const edit of hunkEdits) { if (edit.kind === "equal" || edit.kind === "delete") oldCount++; if (edit.kind === "equal" || edit.kind === "insert") newCount++; } return { oldStart: oldLine, oldCount, newStart: newLine, newCount, edits: hunkEdits }; } var init_diff = __esm({ "src/vcs/diff.ts"() { "use strict"; } }); // src/vcs/merge.ts function threeWayMerge(base, ours, theirs, blobResolver) { const mergedFiles = /* @__PURE__ */ new Map(); const conflicts = []; const allPaths = /* @__PURE__ */ new Set(); for (const [p, s] of base) if (!s.deleted) allPaths.add(p); for (const [p, s] of ours) if (!s.deleted) allPaths.add(p); for (const [p, s] of theirs) if (!s.deleted) allPaths.add(p); for (const [p, s] of ours) if (s.deleted) allPaths.add(p); for (const [p, s] of theirs) if (s.deleted) allPaths.add(p); for (const path of allPaths) { const b = base.get(path); const o = ours.get(path); const t = theirs.get(path); const baseExists = b && !b.deleted; const oursExists = o && !o.deleted; const theirsExists = t && !t.deleted; const baseHash = baseExists ? b.contentHash : void 0; const oursHash = oursExists ? o.contentHash : void 0; const theirsHash = theirsExists ? t.contentHash : void 0; if (oursHash === theirsHash) { continue; } if (theirsHash === baseHash && oursHash !== baseHash) { if (!oursExists) { mergedFiles.set(path, null); } continue; } if (oursHash === baseHash && theirsHash !== baseHash) { if (!theirsExists) { mergedFiles.set(path, null); } else if (theirsHash && blobResolver) { const content = blobResolver?.get(theirsHash); if (content) { mergedFiles.set(path, content.toString("utf-8")); } } continue; } if (!baseExists && oursExists && theirsExists) { if (oursHash === theirsHash) { continue; } const oursContent = oursHash && blobResolver ? blobResolver?.get(oursHash)?.toString("utf-8") : void 0; const theirsContent = theirsHash && blobResolver ? blobResolver?.get(theirsHash)?.toString("utf-8") : void 0; if (oursContent !== void 0 && theirsContent !== void 0) { const textResult = threeWayTextMerge("", oursContent, theirsContent); if (textResult.clean) { mergedFiles.set(path, textResult.merged); continue; } conflicts.push({ path, kind: "add-add", ours: oursContent, theirs: theirsContent, mergedWithMarkers: textResult.merged }); } else { conflicts.push({ path, kind: "add-add", ours: oursContent, theirs: theirsContent }); } continue; } if (oursExists && !theirsExists) { conflicts.push({ path, kind: "modify-delete", ours: oursHash && blobResolver ? blobResolver?.get(oursHash)?.toString("utf-8") : void 0 }); continue; } if (!oursExists && theirsExists) { conflicts.push({ path, kind: "modify-delete", theirs: theirsHash && blobResolver ? blobResolver?.get(theirsHash)?.toString("utf-8") : void 0 }); continue; } if (oursExists && theirsExists && oursHash !== theirsHash) { const baseContent = baseHash && blobResolver ? blobResolver?.get(baseHash)?.toString("utf-8") : void 0; const oursContent = oursHash && blobResolver ? blobResolver?.get(oursHash)?.toString("utf-8") : void 0; const theirsContent = theirsHash && blobResolver ? blobResolver?.get(theirsHash)?.toString("utf-8") : void 0; if (baseContent !== void 0 && oursContent !== void 0 && theirsContent !== void 0) { const textResult = threeWayTextMerge(baseContent, oursContent, theirsContent); if (textResult.clean) { mergedFiles.set(path, textResult.merged); } else { conflicts.push({ path, kind: "modify-modify", base: baseContent, ours: oursContent, theirs: theirsContent, mergedWithMarkers: textResult.merged }); } } else { conflicts.push({ path, kind: "modify-modify", base: baseContent, ours: oursContent, theirs: theirsContent }); } continue; } } const added = [...mergedFiles.values()].filter((v) => v !== null).length; const deleted = [...mergedFiles.values()].filter((v) => v === null).length; return { clean: conflicts.length === 0, mergedFiles, conflicts, stats: { added, modified: added, // in a merge context, additions from theirs are "modified" deleted, conflicted: conflicts.length } }; } function threeWayTextMerge(baseText, oursText, theirsText) { const baseLines = baseText.split("\n"); const oursLines = oursText.split("\n"); const theirsLines = theirsText.split("\n"); const oursChanges = computeLineChanges(baseLines, oursLines); const theirsChanges = computeLineChanges(baseLines, theirsLines); const result = []; let clean = true; let baseIdx = 0; let oursIdx = 0; let theirsIdx = 0; while (baseIdx < baseLines.length || oursIdx < oursLines.length || theirsIdx < theirsLines.length) { const oursChange = oursChanges.get(baseIdx); const theirsChange = theirsChanges.get(baseIdx); if (baseIdx >= baseLines.length) { while (oursIdx < oursLines.length) { result.push(oursLines[oursIdx++]); } while (theirsIdx < theirsLines.length) { result.push(theirsLines[theirsIdx++]); } break; } if (!oursChange && !theirsChange) { result.push(baseLines[baseIdx]); baseIdx++; oursIdx++; theirsIdx++; } else if (oursChange && !theirsChange) { applyChange(oursChange, result); baseIdx += oursChange.baseCount; oursIdx += oursChange.newCount; theirsIdx += oursChange.baseCount; } else if (!oursChange && theirsChange) { applyChange(theirsChange, result); baseIdx += theirsChange.baseCount; oursIdx += theirsChange.baseCount; theirsIdx += theirsChange.newCount; } else if (oursChange && theirsChange) { if (oursChange.baseCount === theirsChange.baseCount && oursChange.newLines.join("\n") === theirsChange.newLines.join("\n")) { applyChange(oursChange, result); baseIdx += oursChange.baseCount; oursIdx += oursChange.newCount; theirsIdx += theirsChange.newCount; } else { clean = false; result.push("<<<<<<< ours"); for (const line of oursChange.newLines) result.push(line); result.push("======="); for (const line of theirsChange.newLines) result.push(line); result.push(">>>>>>> theirs"); baseIdx += Math.max(oursChange.baseCount, theirsChange.baseCount); oursIdx += oursChange.newCount; theirsIdx += theirsChange.newCount; } } } return { clean, merged: result.join("\n") }; } function applyChange(change, result) { for (const line of change.newLines) { result.push(line); } } function computeLineChanges(baseLines, newLines) { const changes = /* @__PURE__ */ new Map(); const matches = lcsMatch(baseLines, newLines); let baseIdx = 0; let newIdx = 0; for (const match of matches) { if (baseIdx < match.baseIdx || newIdx < match.newIdx) { const baseCount = match.baseIdx - baseIdx; const newCount = match.newIdx - newIdx; if (baseCount > 0 || newCount > 0) { changes.set(baseIdx, { baseStart: baseIdx, baseCount, newCount, newLines: newLines.slice(newIdx, newIdx + newCount) }); } } baseIdx = match.baseIdx + 1; newIdx = match.newIdx + 1; } if (baseIdx < baseLines.length || newIdx < newLines.length) { const baseCount = baseLines.length - baseIdx; const newCount = newLines.length - newIdx; if (baseCount > 0 || newCount > 0) { changes.set(baseIdx, { baseStart: baseIdx, baseCount, newCount, newLines: newLines.slice(newIdx) }); } } return changes; } function lcsMatch(a, b) { const n = a.length; const m = b.length; if (n === 0 || m === 0) return []; const dp = Array.from( { length: n + 1 }, () => new Array(m + 1).fill(0) ); for (let i2 = 1; i2 <= n; i2++) { for (let j2 = 1; j2 <= m; j2++) { if (a[i2 - 1] === b[j2 - 1]) { dp[i2][j2] = dp[i2 - 1][j2 - 1] + 1; } else { dp[i2][j2] = Math.max(dp[i2 - 1][j2], dp[i2][j2 - 1]); } } } const matches = []; let i = n; let j = m; while (i > 0 && j > 0) { if (a[i - 1] === b[j - 1]) { matches.unshift({ baseIdx: i - 1, newIdx: j - 1 }); i--; j--; } else if (dp[i - 1][j] > dp[i][j - 1]) { i--; } else { j--; } } return matches; } var init_merge = __esm({ "src/vcs/merge.ts"() { "use strict"; } }); // src/vcs/issue.ts import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync, openSync, closeSync, unlinkSync } from "fs"; import { join as join2, dirname as dirname2 } from "path"; function getIssueCounterPath(rootPath) { return join2(rootPath, ".trellis", "issue-counter.json"); } function getLaneIssueCounterPath(rootPath, laneId) { return join2( rootPath, ".trellis", "issue-counters", `${encodeURIComponent(laneId)}.json` ); } function bumpIssueCounterAtLeast(rootPath, n) { const counterPath = getIssueCounterPath(rootPath); const dir = dirname2(counterPath); if (!existsSync2(dir)) mkdirSync(dir, { recursive: true }); let counter = 0; if (existsSync2(counterPath)) { try { counter = JSON.parse(readFileSync2(counterPath, "utf-8")).counter ?? 0; } catch { } } if (n > counter) { writeFileSync2(counterPath, JSON.stringify({ counter: n }, null, 2)); } } function nextIssueId(rootPath, laneId) { const laneScope = laneId?.trim(); const counterPath = getIssueCounterPath(rootPath); const scopedCounterPath = laneScope ? getLaneIssueCounterPath(rootPath, laneScope) : counterPath; const dir = dirname2(scopedCounterPath); if (!existsSync2(dir)) mkdirSync(dir, { recursive: true }); const lockPath = `${scopedCounterPath}.lock`; const deadline = Date.now() + 5e3; let lockFd; while (Date.now() < deadline) { try { lockFd = openSync(lockPath, "wx"); break; } catch (err) { if (err?.code !== "EEXIST") { throw err; } } } if (lockFd === void 0) { throw new Error( `Timed out waiting for issue counter lock: ${lockPath}. Another Trellis process may be stalled.` ); } try { let counter = 0; if (existsSync2(scopedCounterPath)) { try { counter = JSON.parse(readFileSync2(scopedCounterPath, "utf-8")).counter ?? 0; } catch { } } counter++; writeFileSync2(scopedCounterPath, JSON.stringify({ counter }, null, 2)); if (laneScope) return `issue:${laneScope}:${counter}`; return `TRL-${counter}`; } finally { closeSync(lockFd); try