UNPKG

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
#!/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