{"version":3,"file":"identity.d.ts","sourceRoot":"","sources":["../../../src/core/workspace/identity.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAQH,8CAA8C;AAC9C,eAAO,MAAM,oBAAoB,IAAI,CAAC;AAEtC,MAAM,WAAW,yBAAyB;IACzC,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;CACpB;AAeD;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,MAAM,GAAG,yBAAyB,CA6D/E;AAYD,MAAM,WAAW,YAAa,SAAQ,yBAAyB;IAC9D,YAAY,EAAE,MAAM,CAAC;CACrB;AAED,wBAAgB,cAAc,CAAC,QAAQ,EAAE,yBAAyB,GAAG,YAAY,CAEhF;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,CAUzC;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,CAE3E;AAED,mEAAmE;AACnE,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEjD;AAED,wBAAgB,aAAa,IAAI,MAAM,CAEtC","sourcesContent":["/**\n * Canonical workspace identity resolution.\n *\n * The identity must be stable across symlink/junction-safe realpaths, distinct\n * per repository and per worktree, and carry no secret content. Branch and HEAD\n * are metadata, never the sole identity.\n */\n\nimport { execSync } from \"node:child_process\";\nimport { createHash } from \"node:crypto\";\nimport { existsSync, realpathSync } from \"node:fs\";\nimport { homedir, tmpdir } from \"node:os\";\nimport path from \"node:path\";\n\n/** Current workspace index schema version. */\nexport const INDEX_SCHEMA_VERSION = 1;\n\nexport interface ResolvedWorkspaceIdentity {\n\tworkspaceId: string;\n\tcanonicalRoot: string;\n\tfilesystemIdentity: string;\n\tgitRepositoryId?: string;\n\tgitCommonDir?: string;\n\tworktreeId?: string;\n\tbranch?: string;\n\theadCommit?: string;\n}\n\nfunction safeExec(argv: string[]): string | undefined {\n\ttry {\n\t\tconst out = execSync(argv.join(\" \"), {\n\t\t\tencoding: \"utf-8\",\n\t\t\tstdio: [\"ignore\", \"pipe\", \"pipe\"],\n\t\t\ttimeout: 5000,\n\t\t});\n\t\treturn out.trim() || undefined;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\n/**\n * Resolve canonical root (symlink/junction-safe) and git identity for a path.\n * Never throws for a usable filesystem path.\n */\nexport function resolveWorkspaceIdentity(cwd: string): ResolvedWorkspaceIdentity {\n\tconst abs = path.resolve(cwd);\n\tlet canonicalRoot = abs;\n\ttry {\n\t\tif (existsSync(abs)) canonicalRoot = realpathSync(abs);\n\t} catch {\n\t\tcanonicalRoot = abs;\n\t}\n\n\tconst filesystemIdentity = createHash(\"sha256\").update(canonicalRoot).digest(\"hex\").slice(0, 32);\n\n\tlet gitCommonDir: string | undefined;\n\tlet gitRepositoryId: string | undefined;\n\tlet worktreeId: string | undefined;\n\tlet branch: string | undefined;\n\tlet headCommit: string | undefined;\n\n\tconst common = safeExec([\"rev-parse\", \"--git-common-dir\"]);\n\tconst topLevel = safeExec([\"rev-parse\", \"--show-toplevel\"]);\n\tconst gitDir = safeExec([\"rev-parse\", \"--absolute-git-dir\"]);\n\n\tif (common) {\n\t\tgitCommonDir = path.resolve(abs, common);\n\t\ttry {\n\t\t\tgitCommonDir = realpathSync(gitCommonDir);\n\t\t} catch {\n\t\t\t/* keep resolved form */\n\t\t}\n\t\t// Repository identity is the common dir (shared across worktrees).\n\t\tgitRepositoryId = createHash(\"sha256\").update(gitCommonDir).digest(\"hex\").slice(0, 32);\n\t}\n\n\tif (gitDir && topLevel) {\n\t\t// worktree identity is the actual working git dir when it differs from common.\n\t\tconst resolvedGitDir = path.resolve(abs, gitDir);\n\t\tif (!gitCommonDir || resolvedGitDir !== gitCommonDir) {\n\t\t\tworktreeId = createHash(\"sha256\").update(resolvedGitDir).digest(\"hex\").slice(0, 32);\n\t\t}\n\t\tbranch = safeExec([\"rev-parse\", \"--abbrev-ref\", \"HEAD\"]);\n\t\theadCommit = safeExec([\"rev-parse\", \"HEAD\"]);\n\t}\n\n\tconst topRoot = topLevel ? path.resolve(abs, topLevel) : canonicalRoot;\n\n\t// Stable workspace id: prefer git repository identity, else filesystem identity.\n\tconst identityBase = gitRepositoryId ?? filesystemIdentity;\n\tconst workspaceId = createHash(\"sha256\")\n\t\t.update(`${identityBase}:${worktreeId ?? \"\"}:${normalizeForIdentity(topRoot, worktreeId)}`)\n\t\t.digest(\"hex\")\n\t\t.slice(0, 32);\n\n\treturn {\n\t\tworkspaceId,\n\t\tcanonicalRoot: topRoot,\n\t\tfilesystemIdentity,\n\t\tgitRepositoryId,\n\t\tgitCommonDir,\n\t\tworktreeId,\n\t\tbranch,\n\t\theadCommit,\n\t};\n}\n\n/** Case-normalization appropriate to platform (lowercase on win32). */\nfunction normalizeForIdentity(p: string, worktreeId?: string): string {\n\tlet s = p.replace(/\\\\/g, \"/\").replace(/\\/+$/, \"\");\n\tif (process.platform === \"win32\") s = s.toLowerCase();\n\t// Include worktree id so two worktrees of the same repo don't conflate even\n\t// if the common repo was already used as the primary base.\n\tif (worktreeId) s = `${s}#${worktreeId}`;\n\treturn s;\n}\n\nexport interface IdentityMeta extends ResolvedWorkspaceIdentity {\n\tindexVersion: number;\n}\n\nexport function toIdentityMeta(identity: ResolvedWorkspaceIdentity): IdentityMeta {\n\treturn { ...identity, indexVersion: INDEX_SCHEMA_VERSION };\n}\n\n/**\n * Compute the storage root for workspace index data. Never inside the source\n * directory by default. Per-workspace isolation is achieved with subdirectories\n * keyed by workspace id.\n */\nexport function defaultIndexRoot(): string {\n\tif (process.platform === \"win32\") {\n\t\tconst local = process.env.LOCALAPPDATA || path.join(homedir(), \"AppData\", \"Local\");\n\t\treturn path.join(local, \"jensen\", \"index\");\n\t}\n\tif (process.platform === \"darwin\") {\n\t\treturn path.join(homedir(), \"Library\", \"Caches\", \"jensen\", \"index\");\n\t}\n\tconst xdg = process.env.XDG_CACHE_HOME || path.join(homedir(), \".cache\");\n\treturn path.join(xdg, \"jensen\", \"index\");\n}\n\nexport function workspaceIndexDir(root: string, workspaceId: string): string {\n\treturn path.join(root, workspaceId);\n}\n\n/** Temporary sibling used during atomic generation replacement. */\nexport function tempDirUnder(root: string): string {\n\treturn path.join(root, \".tmp\");\n}\n\nexport function systemTempDir(): string {\n\treturn tmpdir();\n}\n"]}