trellis
Version:
Agentic State Engine — event-sourced causal graph with branching, decision traces, and realtime sync for AI-native applications
1,773 lines (1,756 loc) • 428 kB
JavaScript
#!/usr/bin/env bun
import {
RateLimiter,
WebSocketTransport
} from "../chunk-X7DVO7XR.js";
import {
compareVersions,
computeContentHash,
createLockfile,
findDependents,
latestSatisfying,
readLockfile,
removeFromLockfile,
satisfies,
writeLockfile
} from "../chunk-QQF4WF7W.js";
import "../chunk-OAB6TQOO.js";
import {
init_resolve_runtime_theme_css,
resolveRuntimeThemeCss
} from "../chunk-WJ5T7N6J.js";
import {
init_open_browser,
openBrowser,
open_browser_exports
} from "../chunk-A4LLWEDC.js";
import {
init_node_adapter,
startNodeServer
} from "../chunk-KO2LIUUR.js";
import {
SPRITE_PUBLIC_HTTP_PORT,
buildDeployUrl,
validateDeployName
} from "../chunk-3XCOAP6G.js";
import {
SPRITE_ENSURE_BUN_SH,
assertSpriteCli,
ensureSprite,
ensureSpritePublicAccess,
resolveSpritePublicUrl,
runSpriteCopy,
runSpriteExec,
spriteStartServiceSh,
spriteStopServiceSh
} from "../chunk-HQD7LBM4.js";
import {
seedContext
} from "../chunk-LWSBTWYM.js";
import {
TrellisVcsEngine,
addPeer,
findWaitingOnYou,
formatIssueDescription,
formatPromoteExplain,
formatWhereami,
getActiveContext,
hasProfile,
inferProjectContext,
init_capability,
init_engine,
init_envelope,
init_lane_promote,
init_peer_key_resolver,
init_peer_resolver,
init_profile,
init_whereami,
loadPeers,
loadProfile,
parseProjectRef,
removePeer,
resolvePeer,
updateProfile,
validateEnvelope,
writeAgentScaffold,
writeCheckpoint,
writeIdeScaffold
} from "../chunk-O56VT7VP.js";
import {
JOIN_PREFIX,
decodePayload,
deviceFingerprint,
init_pairing,
listDevices,
markDeviceSeen,
pairAccept,
pairApprove,
pairJoin,
pairStart,
revokeDevice
} from "../chunk-QC5OHKIJ.js";
import "../chunk-DIHRG6LA.js";
import "../chunk-2DFWNMEW.js";
import {
OntologyRegistry,
builtinOntologies,
validateStore
} from "../chunk-ZLFCWNZF.js";
import {
createKernelBackend
} from "../chunk-GQTPDDJ7.js";
import {
parseQuery,
parseSimple
} from "../chunk-LTBGCNC4.js";
import {
attachStandardMiddleware
} from "../chunk-SZ3VAB5P.js";
import {
TrellisKernel
} from "../chunk-PVOECISX.js";
import {
QueryEngine
} from "../chunk-BYTAOXGW.js";
import "../chunk-G3XIHPSQ.js";
import "../chunk-LEGH72HW.js";
import {
JsonOpLog,
LaneOpLog,
addRemote,
cloneRemoteLedger,
getDefaultRemote,
init_destructive_guard,
init_lane,
init_op_log,
init_oplog_remote,
init_signing_middleware,
installPulledOps,
laneDir,
listLaneIds,
listLaneMetas,
listRemoteRepos,
loadLaneMeta,
opsPathForRoot,
pullRemoteLedger,
pushRemoteLedger,
remoteStatus,
requireDestructiveConfirm
} from "../chunk-Q4FKTPX4.js";
import {
createIdentity,
ensurePersonIdentity,
hasIdentity,
hasPersonIdentity,
init_identity,
loadIdentity,
loadPersonIdentity,
saveIdentity,
savePersonIdentity,
toPublicIdentity
} from "../chunk-KFJMKL4Y.js";
import {
DEFAULT_BROWSER_RELAY_PORT,
DEFAULT_BROWSER_RELAY_URL,
DEFAULT_BROWSER_SMOKE_STEPS,
describeSuite,
executeTestCommand,
init_browser_steps,
init_browser_types,
init_browser_verify_client,
init_test_runner,
loadBrowserSteps,
relayHealth,
runBrowserVerifyViaRelay
} from "../chunk-LNCBUJNO.js";
import {
init_test_manifest,
listSuiteIds,
loadTestManifest,
resolveReviewLadder,
resolveSuite,
suiteTimeoutMs
} from "../chunk-PBH357QR.js";
import {
DEFAULT_CONFIG,
init_types
} from "../chunk-E2CFJKLU.js";
import {
BlobStore,
init_blob_store
} from "../chunk-MFZ22U6M.js";
import {
IrohSyncTransport
} from "../chunk-UAQCJ2CF.js";
import {
QuarantineStore,
SyncEngine,
getSyncPolicy,
init_reconciler,
reconciler_exports,
shouldBlockMessage
} from "../chunk-TIRVQJDD.js";
import {
PROVENANCE,
init_canonical_op
} from "../chunk-RUMOVKR4.js";
import {
createVcsOp,
init_ops
} from "../chunk-GRWQPKYK.js";
import {
VectorStore,
buildRAGContext,
embed,
embeddings_exports,
init_auto_embed,
init_embeddings,
init_model,
init_store
} from "../chunk-IHHSOBJS.js";
import {
buildRefIndex,
createResolverContext,
init_links,
init_parser,
links_exports,
parseMarkdownRefs
} from "../chunk-PKPJBCJT.js";
import {
__esm,
__export,
__require,
__toCommonJS
} from "../chunk-2ESYSVXG.js";
// src/ui/server.ts
var server_exports = {};
__export(server_exports, {
startUIServer: () => startUIServer
});
import { readFileSync as readFileSync10, existsSync as existsSync14 } from "fs";
import { join as join18, dirname as dirname6 } from "path";
import { fileURLToPath as fileURLToPath3 } from "url";
function buildGraph(engine) {
const nodes = [];
const edges = [];
const nodeIds = /* @__PURE__ */ new Set();
const files = engine.trackedFiles();
for (const f of files) {
const id = `file:${f.path}`;
nodes.push({
id,
label: f.path,
type: "file",
meta: { contentHash: f.contentHash }
});
nodeIds.add(id);
}
const milestones = engine.listMilestones();
for (const m of milestones) {
const id = `milestone:${m.id}`;
nodes.push({
id,
label: m.message ?? m.id,
type: "milestone",
meta: {
createdAt: m.createdAt,
affectedFiles: m.affectedFiles,
fromOpHash: m.fromOpHash,
toOpHash: m.toOpHash
}
});
nodeIds.add(id);
for (const fp of m.affectedFiles ?? []) {
const fileId = `file:${fp}`;
if (nodeIds.has(fileId)) {
edges.push({
source: id,
target: fileId,
type: "milestone_file"
});
}
}
}
const issues = engine.listIssues();
for (const iss of issues) {
const id = `issue:${iss.id}`;
nodes.push({
id,
label: iss.title ?? iss.id,
type: "issue",
meta: {
status: iss.status,
priority: iss.priority,
labels: iss.labels,
assignee: iss.assignee,
createdAt: iss.createdAt,
description: iss.description,
criteria: iss.criteria
}
});
nodeIds.add(id);
if (iss.branchName) {
const branchId = `branch:${iss.branchName}`;
if (nodeIds.has(branchId)) {
edges.push({
source: id,
target: branchId,
type: "issue_branch"
});
}
}
}
const branches = engine.listBranches();
for (const b of branches) {
const id = `branch:${b.name}`;
if (!nodeIds.has(id)) {
nodes.push({
id,
label: b.name,
type: "branch",
meta: {
isCurrent: b.isCurrent,
createdAt: b.createdAt
}
});
nodeIds.add(id);
}
for (const iss of issues) {
if (iss.branchName === b.name) {
edges.push({
source: `issue:${iss.id}`,
target: id,
type: "issue_branch"
});
}
}
}
try {
const mdFiles = [];
for (const f of files) {
if (f.path.endsWith(".md")) {
const absPath = join18(engine.getRootPath(), f.path);
if (existsSync14(absPath)) {
mdFiles.push({
path: f.path,
content: readFileSync10(absPath, "utf-8")
});
}
}
}
if (mdFiles.length > 0) {
const ctx = createResolverContext({
trackedFiles: () => files,
listIssues: () => issues,
listMilestones: () => milestones
});
const refIndex = buildRefIndex(mdFiles, ctx);
for (const [filePath, refs] of refIndex.outgoing) {
const sourceId = `file:${filePath}`;
if (!nodeIds.has(sourceId)) continue;
for (const ref of refs) {
const targetId = `${ref.namespace}:${ref.target}`;
const candidateIds = [
targetId,
`file:${ref.target}`,
`issue:${ref.target}`,
`milestone:${ref.target}`
];
for (const cid of candidateIds) {
if (nodeIds.has(cid) && cid !== sourceId) {
edges.push({
source: sourceId,
target: cid,
type: "wikilink",
label: ref.target
});
break;
}
}
}
}
}
} catch {
}
return { nodes, edges };
}
function buildTimeline(engine) {
const ops = engine.getOps();
const branches = engine.listBranches();
const milestones = engine.listMilestones();
const checkpoints = engine.listCheckpoints();
const opSummaries = ops.map((op, index) => ({
index,
hash: op.hash?.slice(0, 24) ?? "",
kind: op.kind,
timestamp: op.timestamp,
agentId: op.agentId,
filePath: op.vcs?.filePath,
branchName: op.vcs?.branchName,
message: op.vcs?.message
}));
const milestoneMarkers = milestones.map((m) => {
const toIdx = ops.findIndex((o) => o.hash === m.toOpHash);
return {
id: m.id,
message: m.message,
createdAt: m.createdAt,
atOpIndex: toIdx >= 0 ? toIdx : ops.length - 1,
affectedFiles: m.affectedFiles?.length ?? 0
};
});
const checkpointMarkers = checkpoints.map((c) => {
const atIdx = ops.findIndex((o) => o.hash === c.atOpHash);
return {
id: c.id,
trigger: c.trigger,
createdAt: c.createdAt,
atOpIndex: atIdx >= 0 ? atIdx : ops.length - 1
};
});
return {
ops: opSummaries,
branches: branches.map((b) => ({
name: b.name,
isCurrent: b.isCurrent,
createdAt: b.createdAt
})),
milestones: milestoneMarkers,
checkpoints: checkpointMarkers,
totalOps: ops.length
};
}
function buildStoreOverview(engine) {
const store = engine.getStore();
const stats = store.getStats();
const catalog = store.getCatalog();
const typeFacts = store.getFactsByAttribute("type");
const entityTypes = {};
const entityList = [];
for (const fact of typeFacts) {
const typeName = String(fact.v);
entityTypes[typeName] = (entityTypes[typeName] ?? 0) + 1;
const entityFacts = store.getFactsByEntity(fact.e);
entityList.push({
id: fact.e,
type: typeName,
factCount: entityFacts.length
});
}
return {
stats: {
totalFacts: stats.totalFacts,
totalLinks: stats.totalLinks,
uniqueEntities: stats.uniqueEntities,
uniqueAttributes: stats.uniqueAttributes
},
catalog: catalog.map((c) => ({
attribute: c.attribute,
type: c.type,
cardinality: c.cardinality,
distinctCount: c.distinctCount,
examples: c.examples.slice(0, 3)
})),
entityTypes,
entities: entityList.slice(0, 500)
// Cap for performance
};
}
function buildEntityDetail(engine, entityId) {
const store = engine.getStore();
const facts = store.getFactsByEntity(entityId);
if (facts.length === 0) return null;
const typeFact = facts.find((f) => f.a === "type");
const links = store.getLinksByEntity(entityId);
return {
id: entityId,
type: typeFact?.v ?? "unknown",
facts: facts.map((f) => ({ a: f.a, v: f.v })),
links: links.map((l) => ({
a: l.a,
source: l.e1,
target: l.e2,
direction: l.e1 === entityId ? "outgoing" : "incoming"
}))
};
}
function buildSystemInfo(engine) {
const status = engine.status();
const store = engine.getStore();
const stats = store.getStats();
const embeddingsAvailable = existsSync14(
join18(engine.getRootPath(), ".trellis", "embeddings.db")
);
const blobStoreAvailable = engine.getBlobStore() !== null;
return {
engine: {
rootPath: engine.getRootPath(),
branch: status.branch,
totalOps: status.totalOps,
trackedFiles: status.trackedFiles,
lastOpAt: status.lastOp?.timestamp ?? null
},
store: {
totalFacts: stats.totalFacts,
totalLinks: stats.totalLinks,
uniqueEntities: stats.uniqueEntities,
uniqueAttributes: stats.uniqueAttributes
},
features: {
embeddings: embeddingsAvailable,
blobStore: blobStoreAvailable
},
parsers: [
"typescript",
"javascript",
"python",
"go",
"rust",
"ruby",
"java",
"csharp"
]
};
}
function getNodeDetail(engine, nodeId) {
const [type, ...rest] = nodeId.split(":");
const id = rest.join(":");
switch (type) {
case "file": {
const files = engine.trackedFiles();
const file = files.find((f) => f.path === id);
if (!file) return null;
const ops = engine.log({ filePath: id, limit: 10 });
return {
type: "file",
path: id,
contentHash: file.contentHash,
recentOps: ops.map((o) => ({
kind: o.kind,
timestamp: o.timestamp,
hash: o.hash.slice(0, 24)
}))
};
}
case "milestone": {
const milestones = engine.listMilestones();
const m = milestones.find((ms) => ms.id === id);
if (!m) return null;
return { type: "milestone", ...m };
}
case "issue": {
const issue = engine.getIssue(id);
if (!issue) return null;
return { type: "issue", ...issue };
}
case "branch": {
const branches = engine.listBranches();
const b = branches.find((br) => br.name === id);
if (!b) return null;
return { type: "branch", ...b };
}
default:
return null;
}
}
async function startUIServer(opts) {
const engine = new TrellisVcsEngine({
rootPath: opts.rootPath,
provenance: PROVENANCE.http
});
engine.open();
function findClientHtml() {
const candidates = [];
const push = (...paths) => {
for (const p of paths) candidates.push(p);
};
push(join18(process.cwd(), "dist", "ui", "client.html"));
try {
const moduleDir = dirname6(fileURLToPath3(import.meta.url));
push(
join18(moduleDir, "client.html"),
join18(moduleDir, "..", "ui", "client.html"),
join18(moduleDir, "ui", "client.html")
);
} catch {
}
const argvEntry = process.argv[1];
if (argvEntry) {
const argvDir = dirname6(argvEntry);
push(
join18(argvDir, "client.html"),
join18(argvDir, "..", "ui", "client.html"),
join18(argvDir, "ui", "client.html"),
join18(argvDir, "..", "dist", "ui", "client.html")
);
let dir = argvDir;
for (let i = 0; i < 6; i++) {
push(
join18(dir, "dist", "ui", "client.html"),
join18(dir, "ui", "client.html")
);
dir = dirname6(dir);
}
}
let cwd = process.cwd();
for (let i = 0; i < 8; i++) {
push(
join18(cwd, "dist", "ui", "client.html"),
join18(cwd, "node_modules", "trellis", "dist", "ui", "client.html")
);
const parent = dirname6(cwd);
if (parent === cwd) break;
cwd = parent;
}
for (const p of candidates) {
if (existsSync14(p)) return p;
}
throw new Error(
`Could not find client.html. cwd=${process.cwd()} argv=${argvEntry ?? "(none)"}
Try reinstalling the package or running \`npm run build\`.`
);
}
const clientHtml = readFileSync10(findClientHtml(), "utf-8");
let embeddingManager = null;
async function getEmbeddingManager() {
if (!embeddingManager) {
try {
const { EmbeddingManager } = (init_embeddings(), __toCommonJS(embeddings_exports));
const dbPath = join18(opts.rootPath, ".trellis", "embeddings.db");
if (existsSync14(dbPath)) {
embeddingManager = await EmbeddingManager.create(dbPath);
}
} catch {
}
}
return embeddingManager;
}
const requestedPort = opts.port ?? 3333;
const fetchHandler = async (req) => {
const url = new URL(req.url);
const path = url.pathname;
const headers = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type"
};
if (req.method === "OPTIONS") {
return new Response(null, { status: 204, headers });
}
if (path === "/api/graph") {
const graph = buildGraph(engine);
return Response.json(graph, { headers });
}
if (path === "/api/timeline") {
const timeline = buildTimeline(engine);
return Response.json(timeline, { headers });
}
if (path === "/api/store") {
const overview = buildStoreOverview(engine);
return Response.json(overview, { headers });
}
if (path.startsWith("/api/store/entity/")) {
const entityId = decodeURIComponent(
path.slice("/api/store/entity/".length)
);
const detail = buildEntityDetail(engine, entityId);
if (!detail) {
return Response.json(
{ error: "Entity not found" },
{ status: 404, headers }
);
}
return Response.json(detail, { headers });
}
if (path === "/api/system") {
const info = buildSystemInfo(engine);
return Response.json(info, { headers });
}
if (path === "/api/search") {
const query = url.searchParams.get("q");
if (!query) {
return Response.json(
{ error: "Missing ?q= parameter" },
{ status: 400, headers }
);
}
const limit = parseInt(url.searchParams.get("limit") ?? "10", 10);
const typeFilter = url.searchParams.get("type");
const mgr = await getEmbeddingManager();
if (!mgr) {
return Response.json(
{
results: [],
message: "No embedding index. Run `trellis reindex` first."
},
{ headers }
);
}
try {
const searchOpts = { limit };
if (typeFilter) {
searchOpts.types = typeFilter.split(",").map((t) => t.trim());
}
const results = await mgr.search(query, searchOpts);
return Response.json(
{
results: results.map((r) => ({
score: r.score,
chunkType: r.chunk.chunkType,
filePath: r.chunk.filePath,
entityId: r.chunk.entityId,
content: r.chunk.content
}))
},
{ headers }
);
} catch (err) {
return Response.json({ error: err.message }, { status: 500, headers });
}
}
if (path.startsWith("/api/node/")) {
const nodeId = decodeURIComponent(path.slice("/api/node/".length));
const detail = getNodeDetail(engine, nodeId);
if (!detail) {
return Response.json(
{ error: "Node not found" },
{ status: 404, headers }
);
}
return Response.json(detail, { headers });
}
if (path === "/theme/runtime-theme.css") {
const cssPath = resolveRuntimeThemeCss(opts.rootPath);
if (!cssPath) {
return new Response(
"runtime-theme.css not found \u2014 run from trellis-node.",
{
status: 404,
headers
}
);
}
return new Response(readFileSync10(cssPath, "utf-8"), {
headers: {
...headers,
"Content-Type": "text/css; charset=utf-8",
"Cache-Control": "no-cache"
}
});
}
if (path === "/admin" || path === "/admin.html") {
const adminHtmlPath = join18(dirname6(findClientHtml()), "admin.html");
if (existsSync14(adminHtmlPath)) {
return new Response(readFileSync10(adminHtmlPath, "utf-8"), {
headers: { ...headers, "Content-Type": "text/html; charset=utf-8" }
});
}
return new Response("admin.html not found \u2014 run `npm run build` first.", {
status: 404,
headers
});
}
if (path === "/admin-datatable.css") {
const cssPath = join18(dirname6(findClientHtml()), "admin-datatable.css");
if (existsSync14(cssPath)) {
return new Response(readFileSync10(cssPath, "utf-8"), {
headers: {
...headers,
"Content-Type": "text/css; charset=utf-8",
"Cache-Control": "no-cache"
}
});
}
return new Response("admin-datatable.css not found.", {
status: 404,
headers
});
}
if (path.startsWith("/ui/@trellis.computer/ui/dist/")) {
const uiPath = join18(dirname6(findClientHtml()), path.replace("/ui/", ""));
if (existsSync14(uiPath)) {
return new Response(readFileSync10(uiPath, "utf-8"), {
headers: {
...headers,
"Content-Type": "application/javascript; charset=utf-8",
"Cache-Control": "no-cache"
}
});
}
return new Response("UI file not found.", {
status: 404,
headers
});
}
if (path === "/" || path === "/index.html") {
return new Response(clientHtml, {
headers: { ...headers, "Content-Type": "text/html; charset=utf-8" }
});
}
return new Response("Not Found", { status: 404, headers });
};
const server = await startNodeServer({
port: requestedPort,
fetch: fetchHandler,
// UI server is HTTP-only — no WebSocket needed.
websocket: {
open: () => {
},
message: () => {
},
close: () => {
}
}
});
return {
port: server.port,
stop: () => {
server.stop();
if (embeddingManager) {
try {
embeddingManager.close();
} catch {
}
}
}
};
}
var init_server = __esm({
"src/ui/server.ts"() {
"use strict";
init_node_adapter();
init_engine();
init_canonical_op();
init_links();
init_resolve_runtime_theme_css();
}
});
// src/cli/index.ts
init_engine();
init_canonical_op();
import { Command } from "commander";
import chalk21 from "chalk";
import { resolve as resolve6, join as join19 } from "path";
init_auto_embed();
// src/cli/views.ts
var STATUS_ORDER = [
"backlog",
"queue",
"in_progress",
"paused",
"closed"
];
var PRIORITY_ORDER = ["critical", "high", "medium", "low"];
function acCompletion(issue) {
const total = issue.criteria?.length ?? 0;
if (total === 0) return null;
const passed = issue.criteria.filter((c) => c.status === "passed").length;
return { passed, total };
}
function toRow(issue) {
return { issue, ac: acCompletion(issue) };
}
function priorityRank(p) {
const i = PRIORITY_ORDER.indexOf(p);
return i === -1 ? PRIORITY_ORDER.length : i;
}
function statusRank(s) {
const i = STATUS_ORDER.indexOf(s);
return i === -1 ? STATUS_ORDER.length : i;
}
function compare(a, b, sort) {
switch (sort) {
case "priority":
return priorityRank(a.issue.priority) - priorityRank(b.issue.priority);
case "created":
return (a.issue.createdAt ?? "").localeCompare(b.issue.createdAt ?? "");
case "started":
return (a.issue.startedAt ?? "").localeCompare(b.issue.startedAt ?? "");
case "progress": {
const pa = a.ac ? a.ac.passed / a.ac.total : -1;
const pb = b.ac ? b.ac.passed / b.ac.total : -1;
return pb - pa;
}
case "blocked":
return Number(b.issue.isBlocked) - Number(a.issue.isBlocked);
}
}
function sortRows(rows, sort) {
if (!sort) return rows;
return [...rows].sort((a, b) => {
const primary = compare(a, b, sort);
if (primary !== 0) return primary;
return (a.issue.id ?? "").localeCompare(b.issue.id ?? "");
});
}
function groupKey(issue, groupBy) {
switch (groupBy) {
case "status":
return issue.status ?? "unknown";
case "priority":
return issue.priority ?? "none";
case "assignee":
return issue.assignee ?? "unassigned";
case "label":
return (issue.labels?.length ?? 0) > 0 ? issue.labels.join(",") : "untagged";
}
}
function groupLabel(key, groupBy) {
if (groupBy === "assignee" && key === "unassigned") return "Unassigned";
if (groupBy === "label" && key === "untagged") return "Untagged";
return key;
}
function buildView(issues, opts = {}) {
const rows = sortRows(
issues.map((i) => toRow(i)),
opts.sort
);
if (!opts.groupBy) {
return [{ key: "all", label: "all", rows }];
}
const map = /* @__PURE__ */ new Map();
for (const row of rows) {
const key = groupKey(row.issue, opts.groupBy);
if (!map.has(key)) map.set(key, []);
map.get(key).push(row);
}
const keys = [...map.keys()].sort((a, b) => {
if (opts.groupBy === "status") return statusRank(a) - statusRank(b);
if (opts.groupBy === "priority") return priorityRank(a) - priorityRank(b);
return a.localeCompare(b);
});
return keys.map((key) => ({
key,
label: groupLabel(key, opts.groupBy),
rows: map.get(key)
}));
}
// src/cli/index.ts
init_store();
init_model();
// src/git/git-reader.ts
import { execSync } from "child_process";
import { existsSync } from "fs";
import { join } from "path";
var GitReader = class {
repoPath;
constructor(repoPath) {
this.repoPath = repoPath;
}
/**
* Verifies this is a valid Git repository.
*/
isGitRepo() {
return existsSync(join(this.repoPath, ".git"));
}
/**
* Returns all commits in topological order (oldest first).
*/
readCommits() {
const SEP = "\u2016";
const format = `%H${SEP}%an${SEP}%ae${SEP}%aI${SEP}%P${SEP}%s`;
const raw = this.git(`log --all --reverse --format="${format}"`);
if (!raw.trim()) {
return [];
}
return raw.trim().split("\n").map((line) => {
const parts = line.split(SEP);
return {
hash: parts[0],
authorName: parts[1],
authorEmail: parts[2],
timestamp: parts[3],
parentHashes: parts[4] ? parts[4].split(" ").filter(Boolean) : [],
message: parts[5] ?? ""
};
});
}
/**
* Returns file changes for a specific commit.
* For the root commit (no parents), diffs against empty tree.
*/
readChanges(commitHash, parentHash) {
let raw;
if (parentHash) {
raw = this.git(
`diff-tree -r --name-status --no-commit-id -M ${parentHash} ${commitHash}`
);
} else {
raw = this.git(
`diff-tree -r --root --name-status --no-commit-id -M ${commitHash}`
);
}
if (!raw.trim()) {
return [];
}
return raw.trim().split("\n").map((line) => {
const parts = line.split(" ");
const statusCode = parts[0].charAt(0);
if (statusCode === "R") {
return { status: "R", oldPath: parts[1], path: parts[2] };
}
return { status: statusCode, path: parts[1] };
});
}
/**
* Returns the full content of a file at a specific commit.
*/
readFileContent(commitHash, filePath) {
try {
return Buffer.from(this.gitBuffer(`show ${commitHash}:${filePath}`));
} catch {
return null;
}
}
/**
* Reads all commits with their file changes in topological order.
* This is the main entry point for the import pipeline.
*/
readFullHistory() {
const commits = this.readCommits();
return commits.map((commit) => {
const parentHash = commit.parentHashes[0];
const changes = this.readChanges(commit.hash, parentHash);
return { ...commit, changes };
});
}
/**
* Returns the total number of commits.
*/
commitCount() {
const raw = this.git("rev-list --all --count");
return parseInt(raw.trim(), 10) || 0;
}
/**
* Returns the current branch name.
*/
currentBranch() {
try {
return this.git("rev-parse --abbrev-ref HEAD").trim();
} catch {
return "main";
}
}
/**
* Returns all branch names.
*/
branches() {
const raw = this.git('branch --format="%(refname:short)"');
return raw.trim().split("\n").filter(Boolean);
}
// ---------------------------------------------------------------------------
// Internals
// ---------------------------------------------------------------------------
git(args) {
return execSync(`git -C "${this.repoPath}" ${args}`, {
encoding: "utf-8",
maxBuffer: 100 * 1024 * 1024
// 100MB for large repos
});
}
gitBuffer(args) {
return execSync(`git -C "${this.repoPath}" ${args}`, {
maxBuffer: 100 * 1024 * 1024
});
}
};
// src/git/git-importer.ts
init_engine();
init_ops();
init_canonical_op();
import { existsSync as existsSync2, mkdirSync } from "fs";
import { join as join2 } from "path";
async function importFromGit(opts) {
const startTime = Date.now();
const gitReader = new GitReader(opts.from);
if (!gitReader.isGitRepo()) {
throw new Error(`Not a Git repository: ${opts.from}`);
}
opts.onProgress?.({
phase: "reading",
current: 0,
total: 0,
message: "Reading Git history\u2026"
});
const history = gitReader.readFullHistory();
const branches = gitReader.branches();
const defaultBranch = gitReader.currentBranch();
opts.onProgress?.({
phase: "reading",
current: history.length,
total: history.length,
message: `Read ${history.length} commits`
});
const engine = new TrellisVcsEngine({
rootPath: opts.to,
agentId: opts.agentId ?? `git-import:${opts.from}`,
defaultBranch,
provenance: PROVENANCE.migration
});
const trellisDir = join2(opts.to, ".trellis");
if (!existsSync2(trellisDir)) {
mkdirSync(trellisDir, { recursive: true });
}
const importEngine = new ImportEngine(engine, opts);
await importEngine.createBranch(defaultBranch);
let opsCreated = 1;
const trackedFiles = /* @__PURE__ */ new Set();
for (let i = 0; i < history.length; i++) {
const commit = history[i];
opts.onProgress?.({
phase: "importing",
current: i + 1,
total: history.length,
message: `Importing commit ${i + 1}/${history.length}: ${commit.message.slice(0, 60)}`
});
for (const change of commit.changes) {
const op = await importEngine.convertChange(change, commit);
opsCreated++;
if (change.status === "A" || change.status === "M" || change.status === "R") {
trackedFiles.add(change.path);
}
if (change.status === "D") {
trackedFiles.delete(change.path);
}
if (change.status === "R" && change.oldPath) {
trackedFiles.delete(change.oldPath);
}
}
await importEngine.createMilestone(commit);
opsCreated++;
}
opts.onProgress?.({
phase: "done",
current: history.length,
total: history.length,
message: `Imported ${history.length} commits \u2192 ${opsCreated} ops`
});
return {
commitsImported: history.length,
opsCreated,
filesTracked: trackedFiles.size,
branches,
duration: Date.now() - startTime
};
}
var ImportEngine = class {
engine;
opts;
lastOpHash;
ops = [];
constructor(engine, opts) {
this.engine = engine;
this.opts = opts;
}
async createBranch(name) {
const op = await createVcsOp("vcs:branchCreate", {
agentId: this.agentId(),
previousHash: this.lastOpHash,
vcs: { branchName: name }
});
this.append(op);
}
async convertChange(change, commit) {
const agentId = `identity:${commit.authorEmail}`;
let kind;
switch (change.status) {
case "A":
kind = "vcs:fileAdd";
break;
case "M":
kind = "vcs:fileModify";
break;
case "D":
kind = "vcs:fileDelete";
break;
case "R":
kind = "vcs:fileRename";
break;
}
let contentHash;
if (change.status !== "D") {
contentHash = await this.hashFileAtCommit(commit.hash, change.path);
}
let oldContentHash;
if (change.status === "M" && commit.parentHashes[0]) {
oldContentHash = await this.hashFileAtCommit(
commit.parentHashes[0],
change.path
);
}
const op = await createVcsOp(kind, {
agentId,
previousHash: this.lastOpHash,
vcs: {
filePath: change.path,
oldFilePath: change.oldPath,
contentHash,
oldContentHash
}
});
op.timestamp = commit.timestamp;
this.append(op);
return op;
}
async createMilestone(commit) {
const agentId = `identity:${commit.authorEmail}`;
const milestoneId = `milestone:git:${commit.hash.slice(0, 12)}`;
const op = await createVcsOp("vcs:milestoneCreate", {
agentId,
previousHash: this.lastOpHash,
vcs: {
milestoneId,
message: commit.message
}
});
op.timestamp = commit.timestamp;
this.append(op);
}
append(op) {
this.lastOpHash = op.hash;
this.ops.push(op);
this.flushOps();
}
flushOps() {
const opsPath = join2(this.opts.to, ".trellis", "ops.json");
const configPath = join2(this.opts.to, ".trellis", "config.json");
const trellisDir = join2(this.opts.to, ".trellis");
if (!existsSync2(trellisDir)) {
mkdirSync(trellisDir, { recursive: true });
}
if (!existsSync2(configPath)) {
const config = {
rootPath: this.opts.to,
ignorePatterns: [
"node_modules",
".git",
".trellis",
"dist",
"build",
".DS_Store",
"*.log"
],
debounceMs: 300,
defaultBranch: "main",
agentId: this.agentId(),
createdAt: (/* @__PURE__ */ new Date()).toISOString()
};
const { writeFileSync: writeFileSync7 } = __require("fs");
writeFileSync7(configPath, JSON.stringify(config, null, 2));
}
const { writeFileSync: writeFileSync6 } = __require("fs");
writeFileSync6(opsPath, JSON.stringify(this.ops, null, 2));
}
async hashFileAtCommit(commitHash, filePath) {
try {
const reader = new GitReader(this.opts.from);
const content = reader.readFileContent(commitHash, filePath);
if (!content) {
return void 0;
}
const hashBuffer = await crypto.subtle.digest(
"SHA-256",
content
);
const hashArray = Array.from(new Uint8Array(hashBuffer));
return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
} catch {
return void 0;
}
}
agentId() {
return this.opts.agentId ?? `git-import:${this.opts.from}`;
}
};
// src/git/git-exporter.ts
init_engine();
init_canonical_op();
import { execSync as execSync2 } from "child_process";
import { existsSync as existsSync3, mkdirSync as mkdirSync2, writeFileSync, unlinkSync } from "fs";
import { join as join3, dirname as dirname2 } from "path";
async function exportToGit(opts) {
const startTime = Date.now();
const engine = new TrellisVcsEngine({ rootPath: opts.from, provenance: PROVENANCE.migration });
engine.open();
const blobResolver = engine.getBlobResolver();
if (!blobResolver) {
throw new Error("Blob resolver not available. Re-open the repo first.");
}
const milestones = engine.listMilestones();
const allOps = engine.getOps();
opts.onProgress?.({
phase: "preparing",
current: 0,
total: milestones.length,
message: `Found ${milestones.length} milestones to export`
});
if (milestones.length === 0) {
return {
milestonesExported: 0,
commitsCreated: 0,
duration: Date.now() - startTime
};
}
if (!existsSync3(opts.to)) {
mkdirSync2(opts.to, { recursive: true });
}
const isGitRepo = existsSync3(join3(opts.to, ".git"));
if (!isGitRepo) {
git(opts.to, "init");
git(
opts.to,
`config user.email "${opts.authorEmail ?? "export@trellis.dev"}"`
);
git(
opts.to,
`config user.name "${opts.authorName ?? "TrellisVCS Export"}"`
);
}
let commitsCreated = 0;
const milestoneMap = new Map(milestones.map((m) => [m.id, m]));
const fileStates = /* @__PURE__ */ new Map();
let pendingChanges = false;
let milestoneIdx = 0;
for (const op of allOps) {
if (op.vcs?.filePath) {
switch (op.kind) {
case "vcs:fileAdd":
case "vcs:fileModify":
fileStates.set(op.vcs.filePath, { contentHash: op.vcs.contentHash });
pendingChanges = true;
break;
case "vcs:fileDelete":
fileStates.set(op.vcs.filePath, { deleted: true });
pendingChanges = true;
break;
case "vcs:fileRename":
if (op.vcs.oldFilePath) {
fileStates.set(op.vcs.oldFilePath, { deleted: true });
}
fileStates.set(op.vcs.filePath, { contentHash: op.vcs.contentHash });
pendingChanges = true;
break;
}
}
if (op.kind !== "vcs:milestoneCreate") continue;
const milestoneId = op.vcs?.milestoneId;
const milestone = milestoneId ? milestoneMap.get(milestoneId) : void 0;
milestoneIdx++;
opts.onProgress?.({
phase: "exporting",
current: milestoneIdx,
total: milestones.length,
message: `Exporting milestone ${milestoneIdx}/${milestones.length}: ${(op.vcs?.message ?? "").slice(0, 60)}`
});
for (const [filePath, state] of fileStates.entries()) {
const absPath = join3(opts.to, filePath);
if (state.deleted) {
if (existsSync3(absPath)) {
unlinkSync(absPath);
}
} else if (state.contentHash && blobResolver) {
const content = blobResolver.get(state.contentHash);
if (content) {
const dir = dirname2(absPath);
if (!existsSync3(dir)) {
mkdirSync2(dir, { recursive: true });
}
writeFileSync(absPath, content);
}
}
}
git(opts.to, "add -A");
const status = git(opts.to, "status --porcelain");
if (status.trim().length === 0 && commitsCreated > 0) {
continue;
}
const authorName = opts.authorName ?? extractAuthorName(milestone?.createdBy ?? op.agentId);
const authorEmail = opts.authorEmail ?? extractAuthorEmail(milestone?.createdBy ?? op.agentId);
const message = op.vcs?.message ?? milestone?.message ?? `Milestone ${milestoneId}`;
const date = milestone?.createdAt ?? op.timestamp ?? (/* @__PURE__ */ new Date()).toISOString();
try {
gitWithEnv(
opts.to,
`commit --allow-empty --author="${authorName} <${authorEmail}>" -m "${escapeMessage(message)}"`,
{
GIT_AUTHOR_DATE: date,
GIT_COMMITTER_DATE: date
}
);
commitsCreated++;
pendingChanges = false;
} catch {
}
}
opts.onProgress?.({
phase: "done",
current: milestones.length,
total: milestones.length,
message: `Exported ${commitsCreated} commits from ${milestones.length} milestones`
});
return {
milestonesExported: milestones.length,
commitsCreated,
duration: Date.now() - startTime
};
}
function git(repoPath, command) {
try {
return execSync2(`git -C "${repoPath}" ${command}`, {
encoding: "utf-8",
stdio: ["pipe", "pipe", "pipe"]
});
} catch (err) {
if (err.stdout) return err.stdout;
throw err;
}
}
function gitWithEnv(repoPath, command, extraEnv) {
return execSync2(`git -C "${repoPath}" ${command}`, {
encoding: "utf-8",
stdio: ["pipe", "pipe", "pipe"],
env: { ...process.env, ...extraEnv }
});
}
function escapeMessage(msg) {
return msg.replace(/"/g, '\\"').replace(/\n/g, "\\n");
}
function extractAuthorName(createdBy) {
if (!createdBy) return "TrellisVCS Export";
const id = createdBy.replace("identity:", "");
if (id.includes("@")) {
return id.split("@")[0];
}
return id;
}
function extractAuthorEmail(createdBy) {
if (!createdBy) return "export@trellis.dev";
const id = createdBy.replace("identity:", "");
if (id.includes("@")) {
return id;
}
return `${id}@trellis.dev`;
}
// src/cli/examples.ts
function eqlLiteral(value) {
return JSON.stringify(value);
}
function formatEqlQuery(query) {
const escaped = query.replace(/'/g, "'\\''");
return `trellis query '${escaped}'`;
}
function buildRepoExamples(input) {
const { issues, milestones, branches, files } = input;
const sections = [];
sections.push({
title: "Status & history",
commands: ["trellis -h", "trellis status", "trellis log"]
});
const issueCommands = ["trellis issue list", 'trellis issue create -t "New task"'];
for (const issue of issues.slice(0, 5)) {
issueCommands.push(`trellis issue show ${issue.id}`);
if (issue.status === "backlog" || issue.status === "queue") {
issueCommands.push(`trellis issue start ${issue.id}`);
}
}
sections.push({
title: issues.length ? `Issues (${issues.length})` : "Issues",
commands: issueCommands
});
const milestoneCommands = [
"trellis milestone list",
'trellis milestone create -m "Ship milestone"'
];
for (const m of milestones.slice(0, 3)) {
milestoneCommands.push(`trellis log -n 5 # milestone: ${m.message ?? m.id}`);
}
sections.push({
title: milestones.length ? `Milestones (${milestones.length})` : "Milestones",
commands: milestoneCommands
});
if (branches.length) {
sections.push({
title: `Branches (${branches.length})`,
commands: ["trellis branch -l", ...branches.slice(0, 3).map((b) => `trellis branch ${b.name}`)]
});
}
if (files.length) {
const fileCommands = files.slice(0, 4).map((f) => `trellis log -f ${f.path}`);
sections.push({
title: `Files (${files.length})`,
commands: fileCommands
});
}
const eql = [
formatEqlQuery('find ?e where type = "Issue"'),
formatEqlQuery('find ?e where type = "FileNode"')
];
if (issues.length) {
eql.push(formatEqlQuery('find ?e where type = "Issue" and status = "backlog"'));
eql.push(
formatEqlQuery(
'SELECT ?blocker ?target WHERE { (?blocker "blockedBy" ?target) }'
)
);
eql.push(
formatEqlQuery(
'SELECT ?e WHERE { [?e "type" "Issue"] NOT [?e "assignee" ?a] }'
)
);
eql.push(
formatEqlQuery(
'SELECT ?e ?status ?priority WHERE { [?e "type" "Issue"] [?e "status" ?status] [?e "priority" ?priority] } FILTER ?priority = "critical"'
)
);
const sample = issues[0];
if (sample.title) {
eql.push(formatEqlQuery(`find ?e where title = ${eqlLiteral(sample.title)}`));
}
if (sample.priority) {
eql.push(formatEqlQuery(`find ?e where priority = ${eqlLiteral(sample.priority)}`));
}
eql.push(`trellis fact query -e issue:${sample.id.replace(/^issue:/, "")}`);
eql.push(`trellis link query -e issue:${sample.id.replace(/^issue:/, "")}`);
}
if (files.length) {
const fp = files[0].path;
eql.push(formatEqlQuery(`find ?e where path = ${eqlLiteral(fp)}`));
}
if (milestones.length && milestones[0].message) {
eql.push(formatEqlQuery(`find ?e where type = "Milestone"`));
}
return { sections, eql };
}
// src/identity/index.ts
init_identity();
init_signing_middleware();
init_pairing();
// src/identity/qr.ts
import { encode, renderUnicodeCompact } from "uqr";
function renderPairingQr(payload, opts) {
return renderUnicodeCompact(payload, {
ecc: opts?.ecc ?? "L",
border: opts?.border ?? 1
});
}
// src/identity/index.ts
init_peer_key_resolver();
// src/identity/governance.ts
init_signing_middleware();
init_identity();
// src/identity/index.ts
init_capability();
// src/cli/onboarding.ts
init_identity();
init_profile();
import chalk from "chalk";
function defaultName() {
return process.env.USER ?? "Anonymous";
}
function printExistingInstructions() {
console.log(chalk.yellow("\n Pairing flow (adopt your identity from an existing device):"));
console.log(" 1. On your existing device: trellis pair start");
console.log(" 2. Scan or paste the payload: trellis pair join <payload>");
console.log(" 3. Approve on the existing device (verify the fingerprint)");
console.log(" 4. Finish here: trellis pair accept <auth-payload>");
console.log(chalk.dim(" Your device key signs as your identity \u2014 `~/.trellis/identity.json` is never copied.\n"));
}
async function onboardFirstRun(opts) {
if (hasPersonIdentity() || hasProfile()) {
return { mode: "skip" };
}
if (opts.interactive) {
const { select, input } = await import("@inquirer/prompts");
console.log(chalk.cyan("\n Welcome to Trellis! Let's set up your identity."));
const choice = await select({
message: "Are you new to Trellis, or do you already have an identity?",
choices: [
{
name: "New \u2014 create my identity",
value: "new"
},
{
name: "Existing \u2014 pair with another device to sync my graph",
value: "existing"
}
],
default: "new"
});
if (choice === "new") {
const nameRaw2 = await input({
message: "Your name",
default: defaultName()
});
const displayName3 = nameRaw2.trim() || defaultName();
ensurePersonIdentity({ displayName: displayName3 });
updateProfile({ name: displayName3 });
console.log(
chalk.green(
` \u2713 Identity created at ~/.trellis/identity.json (${displayName3})
`
)
);
return { mode: "new", displayName: displayName3 };
}
printExistingInstructions();
const nameRaw = await input({
message: "Your name (profile only \u2014 identity comes from pairing)",
default: defaultName()
});
const displayName2 = nameRaw.trim() || defaultName();
updateProfile({ name: displayName2 });
return { mode: "existing", displayName: displayName2 };
}
const mode = opts.identityFlag ?? "new";
if (mode === "new") {
const displayName2 = defaultName();
ensurePersonIdentity({ displayName: displayName2 });
updateProfile({ name: displayName2 });
return { mode, displayName: displayName2 };
}
if (mode === "existing") {
printExistingInstructions();
updateProfile({ name: defaultName() });
return { mode };
}
updateProfile({ name: "Unknown" });
console.log(
chalk.yellow(
" \u26A0 Skipping identity onboarding \u2014 this workspace stays anonymous (dev-only). Run `trellis identity init` to create one later."
)
);
return { mode };
}
// src/cli/repo-path.ts
init_engine();
import { readFileSync as readFileSync2, realpathSync } from "fs";
import { dirname as dirname3, resolve } from "path";
import { fileURLToPath } from "url";
import chalk2 from "chalk";
var here = dirname3(fileURLToPath(import.meta.url));
function cliVersion() {
const path = resolve(here, "../../package.json");
try {
const pkg = JSON.parse(readFileSync2(path, "utf8"));
if (typeof pkg.version === "string") return pkg.version;
} catch {
}
return "0.0.0";
}
function canonicalRoot(dir) {
try {
return realpathSync(dir);
} catch {
return dir;
}
}
function findRepoRoot(pathOpt) {
const start = resolve(pathOpt ?? process.cwd());
const home = process.env.HOME || process.env.USERPROFILE || "";
let dir = start;
while (true) {
if (TrellisVcsEngine.isRepo(dir)) {
if (dir === home && start !== home) {
} else {
return canonicalRoot(dir);
}
}
const parent = dirname3(dir);
if (parent === dir) return void 0;
dir = parent;
}
}
function resolveRepoRoot(pathOpt) {
const start = resolve(pathOpt ?? process.cwd());
const found = findRepoRoot(pathOpt);
if (found) return found;
failNotRepo(start, pathOpt);
}
function failNotRepo(start, pathOpt) {
console.error(chalk2.red("Not a TrellisVCS repository."));
console.error(chalk2.dim(` looked from: ${start}`));
if (pathOpt && resolve(pathOpt) !== process.cwd()) {
console.error(chalk2.dim(` -p: ${resolve(pathOpt)}`));
}
console.error(chalk2.dim(` cwd: ${process.cwd()}`));
console.error(
chalk2.dim(
" Hint: run from the repo root, pass -p <path>, or run `trellis init`."
)
);
process.exit(1);
}
// src/cli/errors.ts
import chalk3 from "chalk";
function handleCliError(err) {
if (err && typeof err === "object" && "code" in err) {
const code = String(err.code);
if (code === "commander.helpDisplayed" || code === "commander.version" || code === "commander.help" || code === "commander.versionDisplayed") {
process.exit(0);
}
}
const message = err instanceof Error ? err.message : String(err);
console.error(chalk3.red(`\u2717 ${message}`));
if (process.env.TRELLIS_DEBUG && err instanceof Error && err.stack) {
console.error(chalk3.dim(err.stack));
}
process.exit(1);
}
// src/vcs/init-storage-guard.ts
init_types();
import { existsSync as existsSync4, readFileSync as readFileSync3, readdirSync, statSync } from "fs";
import { join as join4, relative, resolve as resolve2, sep } from "path";
var INIT_INDEX_MAX_FILES = 500;
var INIT_INDEX_MAX_BYTES = 50 * 1024 * 1024;
var UMBRELLA_SEGMENTS = /* @__PURE__ */ new Set([
"Projects",
"Apps",
"Packages",
"Sandbox"
]);
var REPO_MARKERS = [
".git",
"package.json",
"pyproject.toml",
"Cargo.toml",
"go.mod",
"pom.xml",
"build.gradle"
];
function parseIgnoreFile(filePath) {
if (!existsSync4(filePath)) return [];
return readFileSync3(filePath, "utf8").split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
}
function collectInitIgnorePatterns(rootPath) {
return [
.../* @__PURE__ */ new Set([
...DEFAULT_CONFIG.ignorePatterns,
...parseIgnoreFile(join4(rootPath, ".gitignore")),
...parseIgnoreFile(join4(rootPath, ".trellisignore"))
])
];
}
function shouldIgnore(relPath, patterns) {
for (const pattern of patterns) {
if (pattern.startsWith("*.")) {
const ext = pattern.slice(1);
if (relPath.endsWith(ext)) return true;
} else if (relPath.includes(pattern)) {
return true;
}
}
return false;
}
function walkFiles(rootPath, dir, ignorePatterns, into) {
let entries;
try {
entries = readdirSync(dir, { withFileTypes: true });
} catch {
return;
}
for (const entry of entries) {
const fullPath = join4(dir, entry.name);
const relPath = relative(rootPath, fullPath);
if (shouldIgnore(relPath, ignorePatterns)) continue;
if (entry.isDirectory()) {
walkFiles(rootPath, fu