episoda 0.2.67 → 0.2.69

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