@evo-dev/core 0.0.1-alpha.12 → 0.0.1-alpha.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,6 @@
1
+ import { lstatSync, readFileSync, realpathSync } from "node:fs";
1
2
  import { appendFile, mkdir } from "node:fs/promises";
2
- import { dirname, isAbsolute, join, relative } from "node:path";
3
+ import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
3
4
  import { resolveEvoDevPaths } from "../config/paths.ts";
4
5
  import { normalizeTimestamp, sha256Short } from "../utils/index.ts";
5
6
 
@@ -109,6 +110,15 @@ export interface TraceLogTeamContext {
109
110
  projectKey: string;
110
111
  }
111
112
 
113
+ export interface ProjectLogIdentity {
114
+ projectKey: string;
115
+ workspaceKey: string;
116
+ projectRoot: string;
117
+ workspaceRoot: string;
118
+ gitCommonDir: string | null;
119
+ linkedWorktree: boolean;
120
+ }
121
+
112
122
  export interface CliLogInput {
113
123
  phase: EvoDevLogPhase;
114
124
  argv: string[];
@@ -538,6 +548,39 @@ export function resolveTraceTeamContext(input: {
538
548
  }
539
549
 
540
550
  export function resolveProjectLogKey(homeDir: string, repoRoot: string): string {
551
+ return resolveProjectLogIdentity(homeDir, repoRoot).projectKey;
552
+ }
553
+
554
+ export function resolveProjectLogIdentity(homeDir: string, repoRoot: string): ProjectLogIdentity {
555
+ const identityHomeDir = realpathDirectoryOrFallback(homeDir);
556
+ const workspaceRoot = realpathDirectoryOrFallback(repoRoot);
557
+ const workspaceKey = resolvePathProjectLogKey(identityHomeDir, workspaceRoot);
558
+ const gitCommonDir = resolveGitCommonDir(workspaceRoot);
559
+ if (gitCommonDir === null) {
560
+ return {
561
+ projectKey: workspaceKey,
562
+ workspaceKey,
563
+ projectRoot: workspaceRoot,
564
+ workspaceRoot,
565
+ gitCommonDir: null,
566
+ linkedWorktree: false,
567
+ };
568
+ }
569
+
570
+ const commonDirOwnsWorktree = basename(gitCommonDir) === ".git";
571
+ const projectIdentityPath = commonDirOwnsWorktree ? dirname(gitCommonDir) : gitCommonDir;
572
+ const projectRoot = commonDirOwnsWorktree ? projectIdentityPath : workspaceRoot;
573
+ return {
574
+ projectKey: resolvePathProjectLogKey(identityHomeDir, projectIdentityPath),
575
+ workspaceKey,
576
+ projectRoot,
577
+ workspaceRoot,
578
+ gitCommonDir,
579
+ linkedWorktree: workspaceKey !== resolvePathProjectLogKey(identityHomeDir, projectIdentityPath),
580
+ };
581
+ }
582
+
583
+ export function resolvePathProjectLogKey(homeDir: string, repoRoot: string): string {
541
584
  const trimmedHome = stripTrailingSlash(homeDir);
542
585
  const trimmedRepo = stripTrailingSlash(repoRoot);
543
586
  const relativePath = relative(trimmedHome, trimmedRepo);
@@ -554,6 +597,58 @@ export function resolveProjectLogKey(homeDir: string, repoRoot: string): string
554
597
  return sanitizePersistentIdentifier(projectKey, "project");
555
598
  }
556
599
 
600
+ function realpathDirectoryOrFallback(path: string): string {
601
+ try {
602
+ return realpathSync(path);
603
+ } catch {
604
+ if (!isAbsolute(path)) return stripTrailingSlash(path);
605
+ const missingSegments: string[] = [];
606
+ let current = stripTrailingSlash(path);
607
+ for (;;) {
608
+ const parent = dirname(current);
609
+ if (parent === current) return stripTrailingSlash(path);
610
+ missingSegments.unshift(basename(current));
611
+ current = parent;
612
+ try {
613
+ return join(realpathSync(current), ...missingSegments);
614
+ } catch {
615
+ // Continue toward the nearest existing ancestor so deleted worktrees
616
+ // retain the same path identity across symlinked filesystem prefixes.
617
+ }
618
+ }
619
+ }
620
+ }
621
+
622
+ function resolveGitCommonDir(workspaceRoot: string): string | null {
623
+ const markerPath = join(workspaceRoot, ".git");
624
+ let gitDir: string;
625
+ try {
626
+ const marker = lstatSync(markerPath);
627
+ if (marker.isDirectory()) {
628
+ gitDir = realpathSync(markerPath);
629
+ } else if (marker.isFile()) {
630
+ const value = readFileSync(markerPath, "utf8").trim();
631
+ const match = value.match(/^gitdir:\s*(.+)$/u);
632
+ if (match?.[1] === undefined || match[1].includes("\0")) return null;
633
+ gitDir = realpathSync(isAbsolute(match[1]) ? match[1] : resolve(workspaceRoot, match[1]));
634
+ } else {
635
+ return null;
636
+ }
637
+ } catch {
638
+ return null;
639
+ }
640
+
641
+ try {
642
+ const commonDirValue = readFileSync(join(gitDir, "commondir"), "utf8").trim();
643
+ if (commonDirValue === "" || commonDirValue.includes("\0")) return gitDir;
644
+ return realpathSync(
645
+ isAbsolute(commonDirValue) ? commonDirValue : resolve(gitDir, commonDirValue),
646
+ );
647
+ } catch {
648
+ return gitDir;
649
+ }
650
+ }
651
+
557
652
  export function resolveTraceSessionKey(payload: unknown): string {
558
653
  const record = isRecord(payload) ? payload : {};
559
654
  const sessionId = optionalString(record.session_id ?? record.sessionId);