episoda 0.2.67 → 0.2.68

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.
@@ -2730,7 +2730,7 @@ var require_package = __commonJS({
2730
2730
  "package.json"(exports2, module2) {
2731
2731
  module2.exports = {
2732
2732
  name: "episoda",
2733
- version: "0.2.67",
2733
+ version: "0.2.68",
2734
2734
  description: "CLI tool for Episoda local development workflow orchestration",
2735
2735
  main: "dist/index.js",
2736
2736
  types: "dist/index.d.ts",
@@ -8902,6 +8902,23 @@ var Daemon = class _Daemon {
8902
8902
  console.log(`[Daemon] EP949: Received token refresh for ${projectId}`);
8903
8903
  client.updateToken(tokenMsg.accessToken);
8904
8904
  });
8905
+ client.on("machine_uuid_update", async (message) => {
8906
+ const uuidMsg = message;
8907
+ if (uuidMsg.machineUuid) {
8908
+ this.machineUuid = uuidMsg.machineUuid;
8909
+ console.log(`[Daemon] EP1095: Machine UUID updated: ${this.machineUuid}`);
8910
+ await this.cacheMachineUuid(uuidMsg.machineUuid);
8911
+ this.syncMachineProjectPath(projectId, projectPath).catch((err) => {
8912
+ console.warn("[Daemon] EP1095: Deferred project path sync failed:", err.message);
8913
+ });
8914
+ const connection2 = this.connections.get(projectPath);
8915
+ if (connection2) {
8916
+ this.reconcileWorktrees(projectId, projectPath, connection2.client).catch((err) => {
8917
+ console.warn("[Daemon] EP1095: Deferred reconciliation failed:", err.message);
8918
+ });
8919
+ }
8920
+ }
8921
+ });
8905
8922
  client.on("disconnected", (event) => {
8906
8923
  const disconnectEvent = event;
8907
8924
  console.log(`[Daemon] Connection closed for ${projectId}: code=${disconnectEvent.code}, willReconnect=${disconnectEvent.willReconnect}`);
@@ -8984,12 +9001,47 @@ var Daemon = class _Daemon {
8984
9001
  process.on("SIGTERM", () => shutdownHandler("SIGTERM"));
8985
9002
  process.on("SIGINT", () => shutdownHandler("SIGINT"));
8986
9003
  }
9004
+ /**
9005
+ * EP1095: Get git directory and working directory for a project path
9006
+ * Handles both traditional (.git directory) and worktree (.bare directory) structures
9007
+ * Returns { gitDir, workDir } where:
9008
+ * - gitDir: The --git-dir value to use with git commands
9009
+ * - workDir: The directory to run git commands in (cwd)
9010
+ */
9011
+ getGitDirs(projectPath) {
9012
+ const bareDir = path19.join(projectPath, ".bare");
9013
+ const gitPath = path19.join(projectPath, ".git");
9014
+ if (fs18.existsSync(bareDir) && fs18.statSync(bareDir).isDirectory()) {
9015
+ return { gitDir: bareDir, workDir: projectPath };
9016
+ }
9017
+ if (fs18.existsSync(gitPath) && fs18.statSync(gitPath).isDirectory()) {
9018
+ return { gitDir: null, workDir: projectPath };
9019
+ }
9020
+ if (fs18.existsSync(gitPath) && fs18.statSync(gitPath).isFile()) {
9021
+ return { gitDir: null, workDir: projectPath };
9022
+ }
9023
+ const entries = fs18.readdirSync(projectPath, { withFileTypes: true });
9024
+ for (const entry of entries) {
9025
+ if (entry.isDirectory() && entry.name.startsWith("EP")) {
9026
+ const worktreePath = path19.join(projectPath, entry.name);
9027
+ const worktreeGit = path19.join(worktreePath, ".git");
9028
+ if (fs18.existsSync(worktreeGit)) {
9029
+ return { gitDir: null, workDir: worktreePath };
9030
+ }
9031
+ }
9032
+ }
9033
+ if (fs18.existsSync(bareDir)) {
9034
+ return { gitDir: bareDir, workDir: projectPath };
9035
+ }
9036
+ return { gitDir: null, workDir: projectPath };
9037
+ }
8987
9038
  /**
8988
9039
  * EP595: Configure git with user and workspace ID for post-checkout hook
8989
9040
  * EP655: Added machineId for device isolation in multi-device environments
8990
9041
  * EP725: Added projectId for main branch badge tracking
8991
9042
  * EP726: Added machineUuid (UUID) for unified device identification
8992
9043
  * EP1091: Renamed deviceId param to machineUuid for consistency
9044
+ * EP1095: Added worktree structure support (uses .bare as gitDir)
8993
9045
  *
8994
9046
  * This stores the IDs in .git/config so the post-checkout hook can
8995
9047
  * update module.checkout_* fields when git operations happen from terminal.
@@ -8997,29 +9049,31 @@ var Daemon = class _Daemon {
8997
9049
  async configureGitUser(projectPath, userId, workspaceId, machineId, projectId, machineUuid) {
8998
9050
  try {
8999
9051
  const { execSync: execSync9 } = await import("child_process");
9000
- execSync9(`git config episoda.userId ${userId}`, {
9001
- cwd: projectPath,
9052
+ const { gitDir, workDir } = this.getGitDirs(projectPath);
9053
+ const gitCmd = gitDir ? `git --git-dir="${gitDir}"` : "git";
9054
+ execSync9(`${gitCmd} config episoda.userId ${userId}`, {
9055
+ cwd: workDir,
9002
9056
  encoding: "utf8",
9003
9057
  stdio: "pipe"
9004
9058
  });
9005
- execSync9(`git config episoda.workspaceId ${workspaceId}`, {
9006
- cwd: projectPath,
9059
+ execSync9(`${gitCmd} config episoda.workspaceId ${workspaceId}`, {
9060
+ cwd: workDir,
9007
9061
  encoding: "utf8",
9008
9062
  stdio: "pipe"
9009
9063
  });
9010
- execSync9(`git config episoda.machineId ${machineId}`, {
9011
- cwd: projectPath,
9064
+ execSync9(`${gitCmd} config episoda.machineId ${machineId}`, {
9065
+ cwd: workDir,
9012
9066
  encoding: "utf8",
9013
9067
  stdio: "pipe"
9014
9068
  });
9015
- execSync9(`git config episoda.projectId ${projectId}`, {
9016
- cwd: projectPath,
9069
+ execSync9(`${gitCmd} config episoda.projectId ${projectId}`, {
9070
+ cwd: workDir,
9017
9071
  encoding: "utf8",
9018
9072
  stdio: "pipe"
9019
9073
  });
9020
9074
  if (machineUuid) {
9021
- execSync9(`git config episoda.deviceId ${machineUuid}`, {
9022
- cwd: projectPath,
9075
+ execSync9(`${gitCmd} config episoda.deviceId ${machineUuid}`, {
9076
+ cwd: workDir,
9023
9077
  encoding: "utf8",
9024
9078
  stdio: "pipe"
9025
9079
  });
@@ -9031,6 +9085,7 @@ var Daemon = class _Daemon {
9031
9085
  }
9032
9086
  /**
9033
9087
  * EP610: Install git hooks from bundled files
9088
+ * EP1095: Added worktree structure support (hooks in .bare/hooks)
9034
9089
  *
9035
9090
  * Installs post-checkout and pre-commit hooks to enable:
9036
9091
  * - Branch tracking (post-checkout updates module.checkout_* fields)
@@ -9038,10 +9093,29 @@ var Daemon = class _Daemon {
9038
9093
  */
9039
9094
  async installGitHooks(projectPath) {
9040
9095
  const hooks = ["post-checkout", "pre-commit", "post-commit"];
9041
- const hooksDir = path19.join(projectPath, ".git", "hooks");
9096
+ let hooksDir;
9097
+ const bareHooksDir = path19.join(projectPath, ".bare", "hooks");
9098
+ const gitHooksDir = path19.join(projectPath, ".git", "hooks");
9099
+ if (fs18.existsSync(bareHooksDir)) {
9100
+ hooksDir = bareHooksDir;
9101
+ } else if (fs18.existsSync(gitHooksDir) && fs18.statSync(path19.join(projectPath, ".git")).isDirectory()) {
9102
+ hooksDir = gitHooksDir;
9103
+ } else {
9104
+ const parentBareHooks = path19.join(projectPath, "..", ".bare", "hooks");
9105
+ if (fs18.existsSync(parentBareHooks)) {
9106
+ hooksDir = parentBareHooks;
9107
+ } else {
9108
+ console.warn(`[Daemon] Hooks directory not found for: ${projectPath}`);
9109
+ return;
9110
+ }
9111
+ }
9042
9112
  if (!fs18.existsSync(hooksDir)) {
9043
- console.warn(`[Daemon] Hooks directory not found: ${hooksDir}`);
9044
- return;
9113
+ try {
9114
+ fs18.mkdirSync(hooksDir, { recursive: true });
9115
+ } catch (error) {
9116
+ console.warn(`[Daemon] Hooks directory not found and could not create: ${hooksDir}`);
9117
+ return;
9118
+ }
9045
9119
  }
9046
9120
  for (const hookName of hooks) {
9047
9121
  try {