@humanu/orchestra 0.5.76 → 0.5.78

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.
Files changed (84) hide show
  1. package/bin/{gw.js → orchestra-cli.js} +1 -1
  2. package/bin/orchestra.js +2 -2
  3. package/install.js +41 -42
  4. package/package.json +2 -3
  5. package/resources/api/git.sh +4 -444
  6. package/resources/api/tmux.sh +4 -2791
  7. package/resources/prebuilt/linux-x64/{gw-env-copy → env-copy} +0 -0
  8. package/resources/prebuilt/linux-x64/orchestra +0 -0
  9. package/resources/prebuilt/macos-arm64/{gw-env-copy → env-copy} +0 -0
  10. package/resources/prebuilt/macos-arm64/orchestra +0 -0
  11. package/resources/prebuilt/macos-intel/{gw-env-copy → env-copy} +0 -0
  12. package/resources/prebuilt/macos-intel/orchestra +0 -0
  13. package/resources/scripts/{gw.sh → orchestra-cli.sh} +14 -14
  14. package/resources/scripts/orchestra-local.sh +6 -6
  15. package/resources/scripts/{gwr.sh → orchestra.sh} +11 -55
  16. package/resources/scripts/{shell/bridge → server/services}/ai.sh +4 -4
  17. package/resources/scripts/{gw-bridge.sh → server/services/dispatch.sh} +62 -59
  18. package/resources/scripts/server/services/git/api.sh +447 -0
  19. package/resources/scripts/{shell/git/bridge_check_branch.sh → server/services/git/check_branch_api.sh} +1 -1
  20. package/resources/scripts/{shell/git/bridge_create_worktree.sh → server/services/git/create_worktree_api.sh} +3 -3
  21. package/resources/scripts/{shell/git/bridge_create_worktree_from_existing.sh → server/services/git/create_worktree_from_existing_api.sh} +2 -2
  22. package/resources/scripts/{shell/git/bridge_create_worktree_from_remote.sh → server/services/git/create_worktree_from_remote_api.sh} +2 -2
  23. package/resources/scripts/{shell/git/bridge_delete_branch_only.sh → server/services/git/delete_branch_only_api.sh} +1 -1
  24. package/resources/scripts/{shell/git/bridge_delete_worktree.sh → server/services/git/delete_worktree_api.sh} +2 -2
  25. package/resources/scripts/{shell/git/bridge_delete_worktree_only.sh → server/services/git/delete_worktree_only_api.sh} +1 -1
  26. package/resources/scripts/{shell/git/bridge_enhanced_git_status.sh → server/services/git/enhanced_git_status_api.sh} +3 -3
  27. package/resources/scripts/{shell/git/bridge_git_status.sh → server/services/git/git_status_api.sh} +2 -2
  28. package/resources/scripts/{shell/git/bridge_list_worktrees.sh → server/services/git/list_worktrees_api.sh} +2 -2
  29. package/resources/scripts/server/services/git/merge_api.sh +12 -0
  30. package/resources/scripts/{shell/git/bridge_merge_from_primary.sh → server/services/git/merge_from_primary_api.sh} +1 -1
  31. package/resources/scripts/{shell/git/bridge_merge_into_primary.sh → server/services/git/merge_into_primary_api.sh} +1 -1
  32. package/resources/scripts/{shell/git/bridge_primary_branch.sh → server/services/git/primary_branch_api.sh} +1 -1
  33. package/resources/scripts/{shell/git/bridge_rebase_from_primary.sh → server/services/git/rebase_from_primary_api.sh} +1 -1
  34. package/resources/scripts/server/services/git/repo_api.sh +12 -0
  35. package/resources/scripts/{shell/git/bridge_repo_info.sh → server/services/git/repo_info_api.sh} +1 -1
  36. package/resources/scripts/{shell/git/bridge_squash_into_primary.sh → server/services/git/squash_into_primary_api.sh} +1 -1
  37. package/resources/scripts/{shell/git/bridge_switch_worktree.sh → server/services/git/switch_worktree_api.sh} +2 -2
  38. package/resources/scripts/server/services/git/worktree_api.sh +17 -0
  39. package/resources/scripts/{shell/bridge/utils.sh → server/services/json.sh} +23 -23
  40. package/resources/scripts/{shell/bridge → server/services/session}/tmux.sh +14 -14
  41. package/resources/scripts/server/session/tmux_api.sh +2806 -0
  42. package/resources/scripts/services.sh +6 -0
  43. package/resources/scripts/shell/AGENTS.md +63 -74
  44. package/resources/scripts/shell/build/dependencies.sh +33 -0
  45. package/resources/scripts/shell/build/install.sh +7 -0
  46. package/resources/scripts/shell/build/load.sh +10 -0
  47. package/resources/scripts/shell/build/logging.sh +6 -0
  48. package/resources/scripts/shell/build/rust.sh +18 -0
  49. package/resources/scripts/shell/build/services.sh +17 -0
  50. package/resources/scripts/shell/build/{build_usage.sh → usage.sh} +6 -6
  51. package/resources/scripts/shell/cli_load.sh +9 -0
  52. package/resources/scripts/shell/{gw_env_copy.sh → env_copy.sh} +11 -11
  53. package/resources/scripts/shell/env_copy_command.sh +2 -2
  54. package/resources/scripts/shell/git/checkout_worktree.sh +4 -4
  55. package/resources/scripts/shell/git/create_worktree.sh +2 -2
  56. package/resources/scripts/shell/git/delete_worktree.sh +1 -1
  57. package/resources/scripts/shell/git/merge.sh +1 -1
  58. package/resources/scripts/shell/git/repo.sh +1 -1
  59. package/resources/scripts/shell/git/worktree.sh +1 -1
  60. package/resources/scripts/shell/gwr/check-updates.sh +1 -1
  61. package/resources/scripts/shell/gwr_binary.sh +4 -4
  62. package/resources/scripts/shell/gwr_load.sh +1 -1
  63. package/resources/scripts/shell/gwr_services.sh +10 -0
  64. package/resources/scripts/shell/gwr_usage.sh +10 -10
  65. package/resources/scripts/shell/orchestra-command-hook.sh +15 -15
  66. package/resources/scripts/shell/orchestra-local.sh +6 -6
  67. package/resources/scripts/shell/tmux/new_session_command.sh +1 -1
  68. package/bin/gwr.js +0 -10
  69. package/resources/scripts/shell/build/build_bridge.sh +0 -17
  70. package/resources/scripts/shell/build/build_dependencies.sh +0 -33
  71. package/resources/scripts/shell/build/build_install.sh +0 -7
  72. package/resources/scripts/shell/build/build_load.sh +0 -10
  73. package/resources/scripts/shell/build/build_logging.sh +0 -6
  74. package/resources/scripts/shell/build/build_rust.sh +0 -18
  75. package/resources/scripts/shell/git/bridge_merge.sh +0 -12
  76. package/resources/scripts/shell/git/bridge_repo.sh +0 -12
  77. package/resources/scripts/shell/git/bridge_worktree.sh +0 -17
  78. package/resources/scripts/shell/gw_legacy_wrappers.sh +0 -7
  79. package/resources/scripts/shell/gw_load.sh +0 -10
  80. package/resources/scripts/shell/gwr_bridge.sh +0 -10
  81. /package/resources/scripts/shell/{gw_debug.sh → cli_debug.sh} +0 -0
  82. /package/resources/scripts/shell/{gw_err.sh → cli_err.sh} +0 -0
  83. /package/resources/scripts/shell/{gw_have_cmd.sh → cli_have_cmd.sh} +0 -0
  84. /package/resources/scripts/shell/{gw_info.sh → cli_info.sh} +0 -0
@@ -4,7 +4,7 @@ const path = require('path');
4
4
  const { spawnSync } = require('child_process');
5
5
 
6
6
  const installScript = path.join(__dirname, '..', 'install.js');
7
- const result = spawnSync(process.execPath, [installScript, '--invoke=gw', ...process.argv.slice(2)], {
7
+ const result = spawnSync(process.execPath, [installScript, '--invoke=orchestra-cli', ...process.argv.slice(2)], {
8
8
  stdio: 'inherit',
9
9
  });
10
10
  process.exit(result.status ?? 0);
package/bin/orchestra.js CHANGED
@@ -5,8 +5,8 @@ const { spawnSync } = require('child_process');
5
5
 
6
6
  const installScript = path.join(__dirname, '..', 'install.js');
7
7
  const args = process.argv.slice(2);
8
- const upgradeCommands = new Set(['upgrade', 'update', '--update', '--check-updates']);
9
- const invoke = upgradeCommands.has(args[0]) ? 'gw' : 'orchestra';
8
+ const tuiArgs = new Set(['', '--debug', '-d', '--help', '-h', '--version']);
9
+ const invoke = tuiArgs.has(args[0] ?? '') ? 'orchestra' : 'orchestra-cli';
10
10
  const result = spawnSync(process.execPath, [installScript, `--invoke=${invoke}`, ...args], {
11
11
  stdio: 'inherit',
12
12
  });
package/install.js CHANGED
@@ -5,9 +5,9 @@ const fs = require('fs');
5
5
  const path = require('path');
6
6
 
7
7
  const BINARY_NAME = 'orchestra';
8
- const ENV_COPY_NAME = 'gw-env-copy';
9
- const SHELL_SCRIPTS = ['gwr.sh', 'gw.sh', 'gw-bridge.sh', 'orchestra-local.sh'];
10
- const SHELL_DIRS = ['shell'];
8
+ const ENV_COPY_NAME = 'env-copy';
9
+ const SHELL_SCRIPTS = ['orchestra.sh', 'orchestra-cli.sh', 'services.sh', 'orchestra-local.sh'];
10
+ const SHELL_DIRS = ['shell', 'server'];
11
11
  const SUPPORTED_PLATFORMS = {
12
12
  'darwin-x64': 'macos-intel',
13
13
  'darwin-arm64': 'macos-arm64',
@@ -22,7 +22,7 @@ const scriptsDir = path.join(resourcesDir, 'scripts');
22
22
  const apiDir = path.join(resourcesDir, 'api');
23
23
  const prebuiltDir = path.join(resourcesDir, 'prebuilt');
24
24
  const projectRoot = path.resolve(packageRoot, '..', '..');
25
- const hasLocalSource = fs.existsSync(path.join(projectRoot, 'gw-tui'));
25
+ const hasLocalSource = fs.existsSync(path.join(projectRoot, 'tui'));
26
26
 
27
27
  function ensureDir(dir) {
28
28
  if (!fs.existsSync(dir)) {
@@ -116,9 +116,9 @@ function copyApiScripts() {
116
116
  }
117
117
 
118
118
  function linkCompatibilityBinary(binaryPath) {
119
- const gwTuiPath = path.join(distDir, 'gw-tui');
119
+ const tuiPath = path.join(distDir, 'tui');
120
120
  try {
121
- fs.rmSync(gwTuiPath, { force: true });
121
+ fs.rmSync(tuiPath, { force: true });
122
122
  } catch (err) {
123
123
  if (err.code !== 'ENOENT') {
124
124
  throw err;
@@ -126,10 +126,10 @@ function linkCompatibilityBinary(binaryPath) {
126
126
  }
127
127
 
128
128
  try {
129
- fs.symlinkSync(binaryPath, gwTuiPath);
129
+ fs.symlinkSync(binaryPath, tuiPath);
130
130
  } catch (err) {
131
- fs.copyFileSync(binaryPath, gwTuiPath);
132
- fs.chmodSync(gwTuiPath, 0o755);
131
+ fs.copyFileSync(binaryPath, tuiPath);
132
+ fs.chmodSync(tuiPath, 0o755);
133
133
  }
134
134
  }
135
135
 
@@ -206,10 +206,10 @@ function buildFromSource() {
206
206
  }
207
207
 
208
208
  console.log('🔨 Building from source... (this may take a few minutes)');
209
- console.log(' Working directory:', path.join(projectRoot, 'gw-tui'));
209
+ console.log(' Working directory:', path.join(projectRoot, 'tui'));
210
210
 
211
211
  try {
212
- execSync('cargo build --release', { stdio: 'inherit', cwd: path.join(projectRoot, 'gw-tui') });
212
+ execSync('cargo build --release', { stdio: 'inherit', cwd: path.join(projectRoot, 'tui') });
213
213
  } catch (error) {
214
214
  console.error('❌ Build failed.');
215
215
  console.error('');
@@ -224,9 +224,9 @@ function buildFromSource() {
224
224
  process.exit(1);
225
225
  }
226
226
 
227
- const builtBinary = path.join(projectRoot, 'gw-tui', 'target', 'release', 'gw-tui');
227
+ const builtBinary = path.join(projectRoot, 'tui', 'target', 'release', 'tui');
228
228
  if (!fs.existsSync(builtBinary)) {
229
- console.error('❌ Build finished but gw-tui binary was not produced.');
229
+ console.error('❌ Build finished but tui binary was not produced.');
230
230
  console.error(' Expected location:', builtBinary);
231
231
  process.exit(1);
232
232
  }
@@ -236,7 +236,7 @@ function buildFromSource() {
236
236
  fs.chmodSync(destination, 0o755);
237
237
  linkCompatibilityBinary(destination);
238
238
 
239
- const envCopyBinary = path.join(projectRoot, 'gw-tui', 'target', 'release', ENV_COPY_NAME);
239
+ const envCopyBinary = path.join(projectRoot, 'tui', 'target', 'release', ENV_COPY_NAME);
240
240
  if (!fs.existsSync(envCopyBinary)) {
241
241
  console.error(`❌ ${ENV_COPY_NAME} not found after build.`);
242
242
  console.error(' Expected location:', envCopyBinary);
@@ -253,30 +253,31 @@ function assetsReady() {
253
253
  return (
254
254
  fs.existsSync(path.join(distDir, BINARY_NAME)) &&
255
255
  fs.existsSync(path.join(distDir, ENV_COPY_NAME)) &&
256
- fs.existsSync(path.join(distDir, 'gwr.sh'))
256
+ fs.existsSync(path.join(distDir, 'orchestra.sh'))
257
257
  );
258
258
  }
259
259
 
260
- function printShellWrapperInstructions(binaryPath) {
261
- const distPathEscaped = distDir.split('\\').join('\\\\');
262
- const binaryPathEscaped = binaryPath.split('\\').join('\\\\');
263
-
260
+ function printShellWrapperInstructions() {
264
261
  console.log('\n📝 Shell wrapper functions needed for directory switching:\n');
265
- console.log(`# GW Orchestrator shell wrappers
266
- gwr() {
267
- local dist_path="${distPathEscaped}"
268
- local bin_path="${binaryPathEscaped}"
269
- local out="$(GW_TUI_BIN="$bin_path" bash "$dist_path/gwr.sh" "$@")"
270
- local status=$?
271
- local cd_line="$(echo "$out" | grep -m1 '^cd')"
272
- [[ -n $cd_line ]] && eval "$cd_line"
273
- echo "$out" | grep -v '^cd'
274
- return $status
262
+ console.log(`# Orchestra shell wrappers
263
+ orchestra() {
264
+ case "\${1:-}" in
265
+ ""|"--debug"|"-d"|"--help"|"-h"|"--version")
266
+ command orchestra "$@"
267
+ ;;
268
+ *)
269
+ local out="$(command orchestra-cli "$@")"
270
+ local status=$?
271
+ local cd_line="$(echo "$out" | grep -m1 '^cd')"
272
+ [[ -n $cd_line ]] && eval "$cd_line"
273
+ echo "$out" | grep -v '^cd'
274
+ return $status
275
+ ;;
276
+ esac
275
277
  }
276
278
 
277
- gw() {
278
- local dist_path="${distPathEscaped}"
279
- local out="$(bash "$dist_path/gw.sh" "$@")"
279
+ orchestra-cli() {
280
+ local out="$(command orchestra-cli "$@")"
280
281
  local status=$?
281
282
  local cd_line="$(echo "$out" | grep -m1 '^cd')"
282
283
  [[ -n $cd_line ]] && eval "$cd_line"
@@ -300,13 +301,11 @@ function performSetup({ quiet = false, showInstructions = false } = {}) {
300
301
  buildFromSource();
301
302
  }
302
303
 
303
- const binaryPath = path.join(distDir, BINARY_NAME);
304
-
305
304
  if (!quiet) {
306
305
  console.log('\n✅ Installation complete!');
307
306
  }
308
307
  if (showInstructions) {
309
- printShellWrapperInstructions(binaryPath);
308
+ printShellWrapperInstructions();
310
309
  }
311
310
  }
312
311
 
@@ -320,12 +319,12 @@ function runCommand(command, args) {
320
319
  ensureAssets();
321
320
 
322
321
  let scriptName;
323
- if (command === 'gw') {
324
- scriptName = 'gw.sh';
322
+ if (command === 'orchestra-cli') {
323
+ scriptName = 'orchestra-cli.sh';
325
324
  } else if (command === 'orchestra-local') {
326
325
  scriptName = 'orchestra-local.sh';
327
326
  } else {
328
- scriptName = 'gwr.sh';
327
+ scriptName = 'orchestra.sh';
329
328
  }
330
329
  const scriptPath = path.join(distDir, scriptName);
331
330
  if (!fs.existsSync(scriptPath)) {
@@ -334,7 +333,7 @@ function runCommand(command, args) {
334
333
  }
335
334
 
336
335
  const env = { ...process.env };
337
- if (command !== 'gw' && command !== 'orchestra-local') {
336
+ if (command !== 'orchestra-cli' && command !== 'orchestra-local') {
338
337
  env.GW_TUI_BIN = path.join(distDir, BINARY_NAME);
339
338
  }
340
339
 
@@ -384,12 +383,12 @@ function main() {
384
383
 
385
384
  if (invoke) {
386
385
  let command;
387
- if (invoke === 'gw') {
388
- command = 'gw';
386
+ if (invoke === 'orchestra-cli') {
387
+ command = 'orchestra-cli';
389
388
  } else if (invoke === 'orchestra-local') {
390
389
  command = 'orchestra-local';
391
390
  } else {
392
- command = 'gwr';
391
+ command = 'orchestra';
393
392
  }
394
393
  runCommand(command, args);
395
394
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@humanu/orchestra",
3
- "version": "0.5.76",
3
+ "version": "0.5.78",
4
4
  "description": "AI-powered Git worktree and tmux session manager with modern TUI",
5
5
  "keywords": [
6
6
  "git",
@@ -27,8 +27,7 @@
27
27
  "main": "install.js",
28
28
  "bin": {
29
29
  "orchestra": "bin/orchestra.js",
30
- "gwr": "bin/gwr.js",
31
- "gw": "bin/gw.js",
30
+ "orchestra-cli": "bin/orchestra-cli.js",
32
31
  "orchestra-local": "bin/orchestra-local.js"
33
32
  },
34
33
  "files": [
@@ -1,447 +1,7 @@
1
1
  #!/bin/bash
2
2
 
3
- ###############################################################################
4
- # git.sh – Git Worktree API Module
5
- # ---------------------------------------------------------------------------
6
- # This module provides a centralized API for all Git operations used by
7
- # gw.sh, commands.sh, and gw-bridge.sh. It abstracts Git worktree management
8
- # and other Git operations into a clean, consistent interface.
9
- ###############################################################################
3
+ # shellcheck shell=bash
10
4
 
11
- # --------------------------- Repository Information -------------------------
12
-
13
- # Get the Git repository root directory
14
- git_repo_root() {
15
- git rev-parse --show-toplevel 2>/dev/null || true
16
- }
17
-
18
- # Check if current directory is in a Git repository
19
- git_is_repo() {
20
- git rev-parse --git-dir >/dev/null 2>&1
21
- }
22
-
23
- # Get current absolute path (resolved)
24
- git_current_path() {
25
- pwd -P
26
- }
27
-
28
- # Get the primary repository root (shared across all worktrees)
29
- git_shared_root() {
30
- local common_dir
31
- common_dir="$(git rev-parse --git-common-dir 2>/dev/null)" || return 1
32
- if [[ -z "$common_dir" ]]; then
33
- return 1
34
- fi
35
- if [[ "$common_dir" != /* ]]; then
36
- common_dir="$(pwd -P)/$common_dir"
37
- fi
38
- dirname "$common_dir"
39
- }
40
-
41
- # Convert branch name to slug (replace / with -)
42
- git_branch_to_slug() {
43
- echo "$1" | tr '/' '-'
44
- }
45
-
46
- # --------------------------- Branch Operations ------------------------------
47
-
48
- # Check if a branch exists
49
- git_branch_exists() {
50
- local branch_name="$1"
51
- git rev-parse --verify "$branch_name" >/dev/null 2>&1
52
- }
53
-
54
- # Get current branch name (or empty if detached HEAD)
55
- git_current_branch() {
56
- git symbolic-ref --short HEAD 2>/dev/null || true
57
- }
58
-
59
- # Get current commit SHA (short format)
60
- git_current_commit_short() {
61
- git rev-parse --short HEAD 2>/dev/null || true
62
- }
63
-
64
- # Delete a local branch (force delete)
65
- git_delete_branch() {
66
- local branch_name="$1"
67
- git branch -D "$branch_name" 2>/dev/null || true
68
- }
69
-
70
- # --------------------------- Worktree Operations ----------------------------
71
-
72
- # Ensure ignore rules for worktrees directory exist in both .gitignore and .git/info/exclude
73
- # Non-destructive and idempotent; creates files if needed
74
- git_ensure_ignore_worktrees() {
75
- local root; root="$(git_repo_root)"
76
- [[ -z "$root" ]] && return 1
77
-
78
- # .git/info/exclude (local-only)
79
- local exclude_file="$root/.git/info/exclude"
80
- mkdir -p "$root/.git/info" 2>/dev/null || true
81
- if [[ -f "$exclude_file" ]]; then
82
- if ! grep -q -E '^worktrees/(\s*)?$' "$exclude_file" 2>/dev/null; then
83
- echo 'worktrees/' >> "$exclude_file"
84
- fi
85
- else
86
- echo 'worktrees/' >> "$exclude_file"
87
- fi
88
-
89
- # Top-level .gitignore (shared)
90
- local gi_file="$root/.gitignore"
91
- if [[ -f "$gi_file" ]]; then
92
- if ! grep -q -E '^worktrees/(\s*)?$' "$gi_file" 2>/dev/null; then
93
- echo 'worktrees/' >> "$gi_file"
94
- fi
95
- else
96
- echo 'worktrees/' > "$gi_file"
97
- fi
98
- }
99
-
100
- # List all worktrees: returns lines with "path\tbranch\tsha_short"
101
- git_list_worktrees() {
102
- local root; root="$(git_repo_root)"
103
- [[ -z "$root" ]] && return 1
104
-
105
- local current_path=""
106
- local current_branch=""
107
- local current_head=""
108
-
109
- while IFS= read -r line || [[ -n "$line" ]]; do
110
- case "$line" in
111
- worktree\ *)
112
- current_path="${line#worktree }"
113
- ;;
114
- HEAD\ *)
115
- current_head="${line#HEAD }"
116
- ;;
117
- branch\ *)
118
- current_branch="${line#branch }"
119
- if [[ "$current_branch" == refs/heads/* ]]; then
120
- current_branch="${current_branch#refs/heads/}"
121
- fi
122
- ;;
123
- *)
124
- ;;
125
- esac
126
-
127
- if [[ -z "$line" ]]; then
128
- if [[ -n "$current_path" ]]; then
129
- printf '%s\t%s\t%s\n' "$current_path" "$current_branch" "${current_head:0:7}"
130
- fi
131
- current_path=""
132
- current_branch=""
133
- current_head=""
134
- fi
135
- done < <(cd "$root" && git worktree list --porcelain)
136
-
137
- if [[ -n "$current_path" ]]; then
138
- printf '%s\t%s\t%s\n' "$current_path" "$current_branch" "${current_head:0:7}"
139
- fi
140
- }
141
-
142
- # Get branch name for a given worktree path
143
- git_worktree_path_to_branch() {
144
- local path="$1"
145
- local root; root="$(git_repo_root)"
146
- [[ -z "$root" ]] && return 1
147
-
148
- local current_path=""
149
- local current_branch=""
150
-
151
- while IFS= read -r line || [[ -n "$line" ]]; do
152
- case "$line" in
153
- worktree\ *)
154
- current_path="${line#worktree }"
155
- ;;
156
- branch\ *)
157
- current_branch="${line#branch }"
158
- if [[ "$current_branch" == refs/heads/* ]]; then
159
- current_branch="${current_branch#refs/heads/}"
160
- fi
161
- ;;
162
- *)
163
- ;;
164
- esac
165
-
166
- if [[ -z "$line" ]]; then
167
- if [[ "$current_path" == "$path" ]]; then
168
- printf '%s\n' "$current_branch"
169
- return 0
170
- fi
171
- current_path=""
172
- current_branch=""
173
- fi
174
- done < <(cd "$root" && git worktree list --porcelain)
175
-
176
- return 1
177
- }
178
-
179
- # Get worktree path for a given branch name
180
- git_branch_to_worktree_path() {
181
- local branch_name="$1"
182
- local root; root="$(git_repo_root)"
183
- [[ -z "$root" ]] && return 1
184
-
185
- local current_path=""
186
- local current_branch=""
187
-
188
- while IFS= read -r line || [[ -n "$line" ]]; do
189
- case "$line" in
190
- worktree\ *)
191
- current_path="${line#worktree }"
192
- ;;
193
- branch\ *)
194
- current_branch="${line#branch }"
195
- if [[ "$current_branch" == refs/heads/* ]]; then
196
- current_branch="${current_branch#refs/heads/}"
197
- fi
198
- ;;
199
- *)
200
- ;;
201
- esac
202
-
203
- if [[ -z "$line" ]]; then
204
- if [[ "$current_branch" == "$branch_name" ]]; then
205
- printf '%s\n' "$current_path"
206
- return 0
207
- fi
208
- current_path=""
209
- current_branch=""
210
- fi
211
- done < <(cd "$root" && git worktree list --porcelain)
212
-
213
- return 1
214
- }
215
-
216
- # Create a new worktree with a new branch
217
- git_create_worktree_with_branch() {
218
- local branch_name="$1"
219
- local worktree_path="$2"
220
- local root; root="$(git_repo_root)"
221
- [[ -z "$root" ]] && return 1
222
-
223
- local relative_path="${worktree_path#$root/}"
224
- (cd "$root" && git worktree add "$relative_path" -b "$branch_name" >&2)
225
- }
226
-
227
- # Create a new worktree from existing branch
228
- git_create_worktree_from_branch() {
229
- local branch_name="$1"
230
- local worktree_path="$2"
231
- local root; root="$(git_repo_root)"
232
- [[ -z "$root" ]] && return 1
233
-
234
- local relative_path="${worktree_path#$root/}"
235
- (cd "$root" && git worktree add "$relative_path" "$branch_name" >&2)
236
- }
237
-
238
- git_create_worktree_for_existing_branch() {
239
- local branch_name="$1"
240
- local root; root="$(git_repo_root)"
241
- [[ -z "$root" ]] && return 1
242
-
243
- git_branch_exists "$branch_name" || { echo "Branch does not exist: $branch_name" >&2; return 1; }
244
-
245
- git_ensure_ignore_worktrees >/dev/null 2>&1 || true
246
-
247
- local worktree_path; worktree_path="$(git_build_worktree_path "$branch_name")"
248
- mkdir -p "$(dirname "$worktree_path")"
249
-
250
- git_create_worktree_from_branch "$branch_name" "$worktree_path" || return 1
251
-
252
- echo "$worktree_path"
253
- }
254
-
255
- git_create_worktree_from_remote_branch() {
256
- local branch_name="$1"
257
- local remote_name="${2:-origin}"
258
- local root; root="$(git_repo_root)"
259
- [[ -z "$root" ]] && return 1
260
-
261
- git_ensure_ignore_worktrees >/dev/null 2>&1 || true
262
-
263
- local worktree_path; worktree_path="$(git_build_worktree_path "$branch_name")"
264
- mkdir -p "$(dirname "$worktree_path")"
265
-
266
- local relative_path="${worktree_path#$root/}"
267
- (cd "$root" && git worktree add "$relative_path" -b "$branch_name" "$remote_name/$branch_name" >&2) || return 1
268
-
269
- echo "$worktree_path"
270
- }
271
-
272
- # Remove a worktree (with force fallback)
273
- git_remove_worktree() {
274
- local worktree_path="$1"
275
- local root; root="$(git_repo_root)"
276
- [[ -z "$root" ]] && return 1
277
-
278
- local relative_path="${worktree_path#$root/}"
279
- (cd "$root" && {
280
- git worktree remove "$relative_path" 2>/dev/null || \
281
- git worktree remove --force "$relative_path" 2>/dev/null || true
282
- })
283
- }
284
-
285
- # Get Git status for current directory
286
- git_status() {
287
- git status "$@"
288
- }
289
-
290
- # Get short Git status for current directory
291
- git_status_short() {
292
- git status --short
293
- }
294
-
295
- # --------------------------- Worktree Path Utilities ------------------------
296
-
297
- # Build standard worktree path for a branch
298
- git_build_worktree_path() {
299
- local branch_name="$1"
300
- local root; root="$(git_repo_root)"
301
- [[ -z "$root" ]] && return 1
302
-
303
- local slug; slug="$(git_branch_to_slug "$branch_name")"
304
- echo "$root/worktrees/$slug"
305
- }
306
-
307
- # Check if a worktree exists for a branch
308
- git_worktree_exists_for_branch() {
309
- local branch_name="$1"
310
- local path; path="$(git_branch_to_worktree_path "$branch_name")"
311
- [[ -n "$path" ]]
312
- }
313
-
314
- # Get or create worktree path for a branch
315
- git_ensure_worktree_for_branch() {
316
- local branch_name="$1"
317
- local root; root="$(git_repo_root)"
318
- [[ -z "$root" ]] && return 1
319
-
320
- # Check if worktree already exists
321
- local existing_path; existing_path="$(git_branch_to_worktree_path "$branch_name")"
322
- if [[ -n "$existing_path" ]]; then
323
- echo "$existing_path"
324
- return 0
325
- fi
326
-
327
- # Create worktree if branch exists
328
- if git_branch_exists "$branch_name"; then
329
- # Ensure ignores before creating worktree
330
- git_ensure_ignore_worktrees >/dev/null 2>&1 || true
331
- local worktree_path; worktree_path="$(git_build_worktree_path "$branch_name")"
332
- mkdir -p "$(dirname "$worktree_path")"
333
- git_create_worktree_from_branch "$branch_name" "$worktree_path"
334
- echo "$worktree_path"
335
- return 0
336
- fi
337
-
338
- return 1
339
- }
340
-
341
- # Create new branch and worktree
342
- git_create_branch_and_worktree() {
343
- local branch_name="$1"
344
- local root; root="$(git_repo_root)"
345
- [[ -z "$root" ]] && return 1
346
-
347
- # Ensure ignores before creating worktree
348
- git_ensure_ignore_worktrees >/dev/null 2>&1 || true
349
- local worktree_path; worktree_path="$(git_build_worktree_path "$branch_name")"
350
- mkdir -p "$(dirname "$worktree_path")"
351
- git_create_worktree_with_branch "$branch_name" "$worktree_path"
352
- echo "$worktree_path"
353
- }
354
-
355
- # --------------------------- Validation Functions ---------------------------
356
-
357
- # Validate that we're in a Git repository
358
- git_require_repo() {
359
- git_is_repo || { echo "❌ Not a git repository or git command not found." >&2; return 1; }
360
- }
361
-
362
- # Get repository root with validation
363
- git_require_repo_root() {
364
- local root; root="$(git_repo_root)"
365
- [[ -z "$root" ]] && { echo "❌ Not a git repository" >&2; return 1; }
366
- echo "$root"
367
- }
368
-
369
- # --------------------------- Merge/Rebase Helpers ----------------------------
370
-
371
- # Detect the primary branch name (default remote HEAD), fall back to common names
372
- # Echoes the branch name on success; returns non-zero on failure
373
- git_primary_branch() {
374
- local head_ref branch
375
- # Try origin/HEAD symbolic ref
376
- head_ref=$(git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null || true)
377
- if [[ -n "$head_ref" ]]; then
378
- # refs/remotes/origin/main -> extract last component
379
- branch="${head_ref##*/}"
380
- [[ -n "$branch" ]] && { echo "$branch"; return 0; }
381
- fi
382
- # Try parsing `git remote show origin`
383
- branch=$(git remote show origin 2>/dev/null | awk -F': ' '/HEAD branch/ {print $2; exit}')
384
- if [[ -n "$branch" && "$branch" != "(unknown)" ]]; then
385
- echo "$branch"; return 0
386
- fi
387
- # Fallback candidates
388
- for cand in main master trunk; do
389
- if git rev-parse --verify "$cand" >/dev/null 2>&1 || git rev-parse --verify "origin/$cand" >/dev/null 2>&1; then
390
- echo "$cand"; return 0
391
- fi
392
- done
393
- return 1
394
- }
395
-
396
- # Check if a worktree at path is clean (no changes)
397
- # Usage: git_is_worktree_clean <path>
398
- git_is_worktree_clean() {
399
- local path="$1"
400
- [[ -z "$path" ]] && return 1
401
- local out
402
- out=$(git -C "$path" status --porcelain 2>/dev/null || true)
403
- [[ -z "$out" ]]
404
- }
405
-
406
- # Fetch from origin with prune for a given path
407
- git_fetch_prune() {
408
- local path="$1"
409
- git -C "$path" fetch origin --prune 2>&1
410
- }
411
-
412
- # Fast-forward-only pull for the currently checked out branch at path
413
- # Returns non-zero if not a fast-forward
414
- # Usage: git_pull_ff_only <path>
415
- git_pull_ff_only() {
416
- local path="$1"
417
- git -C "$path" pull --ff-only 2>&1
418
- }
419
-
420
- # Merge a ref into the currently checked out branch at target path
421
- # Usage: git_merge_into <target_path> <ref>
422
- git_merge_into() {
423
- local target="$1"; local ref="$2"
424
- git -C "$target" merge "$ref" 2>&1
425
- }
426
-
427
- # Rebase the current branch at target path onto upstream ref
428
- # Usage: git_rebase_onto <target_path> <upstream_ref>
429
- git_rebase_onto() {
430
- local target="$1"; local upstream="$2"
431
- git -C "$target" rebase "$upstream" 2>&1
432
- }
433
-
434
- # Squash-merge branch into the primary worktree path, then commit if changes staged
435
- # Usage: git_squash_merge_into <primary_path> <branch>
436
- git_squash_merge_into() {
437
- local primary_path="$1"; local branch="$2"
438
- git -C "$primary_path" merge --squash "$branch" 2>&1
439
- }
440
-
441
- # Ensure a primary worktree exists; echoes the path
442
- # Usage: git_ensure_primary_worktree
443
- git_ensure_primary_worktree() {
444
- local primary
445
- primary=$(git_primary_branch) || return 1
446
- git_ensure_worktree_for_branch "$primary"
447
- }
5
+ _ORCHESTRA_API_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
6
+ # shellcheck source=/dev/null
7
+ source "$_ORCHESTRA_API_DIR/../server/services/git/api.sh"