@nemus-cli/nemus 0.2.1

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 (320) hide show
  1. package/CHANGELOG.md +86 -0
  2. package/LICENSE +21 -0
  3. package/README.md +275 -0
  4. package/assets/README.md +53 -0
  5. package/assets/banner.png +0 -0
  6. package/assets/banner.svg +66 -0
  7. package/assets/favicon-32.png +0 -0
  8. package/assets/favicon-64.png +0 -0
  9. package/assets/icon-256.png +0 -0
  10. package/assets/icon.svg +23 -0
  11. package/assets/logo-256.png +0 -0
  12. package/assets/logo-512.png +0 -0
  13. package/assets/logo-wordmark.svg +35 -0
  14. package/assets/logo.svg +32 -0
  15. package/bin/workspace.js +130 -0
  16. package/dist/cli/ai-prompt.js +421 -0
  17. package/dist/cli/dashboard/AgentList.js +61 -0
  18. package/dist/cli/dashboard/DashboardSidebar.js +280 -0
  19. package/dist/cli/dashboard/HelpFooter.js +73 -0
  20. package/dist/cli/dashboard/StatusBadge.js +50 -0
  21. package/dist/cli/dashboard/index.js +11 -0
  22. package/dist/cli/dashboard/types.js +2 -0
  23. package/dist/cli/tui.js +48 -0
  24. package/dist/commands/ai-prompt.js +22 -0
  25. package/dist/commands/analyze-deps.js +138 -0
  26. package/dist/commands/archive.js +113 -0
  27. package/dist/commands/branch/create.js +119 -0
  28. package/dist/commands/branch/index.js +104 -0
  29. package/dist/commands/branch/merge.js +90 -0
  30. package/dist/commands/branch/rebase.js +84 -0
  31. package/dist/commands/branch/switch.js +165 -0
  32. package/dist/commands/cache/index.js +80 -0
  33. package/dist/commands/cache/manager.js +198 -0
  34. package/dist/commands/cleanup.js +153 -0
  35. package/dist/commands/configure-claude.js +55 -0
  36. package/dist/commands/configure.js +231 -0
  37. package/dist/commands/create.js +204 -0
  38. package/dist/commands/dashboard/hook-handler.js +162 -0
  39. package/dist/commands/dashboard/index.js +258 -0
  40. package/dist/commands/dashboard/launcher.js +40 -0
  41. package/dist/commands/dashboard/session-picker.js +106 -0
  42. package/dist/commands/dashboard/sidebar.js +39 -0
  43. package/dist/commands/dashboard/workspace-picker.js +95 -0
  44. package/dist/commands/delete.js +194 -0
  45. package/dist/commands/deprecated-aliases.js +44 -0
  46. package/dist/commands/diff.js +129 -0
  47. package/dist/commands/doctor.js +141 -0
  48. package/dist/commands/generate-docs.js +77 -0
  49. package/dist/commands/ghq-status.js +72 -0
  50. package/dist/commands/go.js +154 -0
  51. package/dist/commands/history.js +124 -0
  52. package/dist/commands/list.js +191 -0
  53. package/dist/commands/mcp/index.js +69 -0
  54. package/dist/commands/migrate.js +296 -0
  55. package/dist/commands/remove-repo.js +217 -0
  56. package/dist/commands/report-bug.js +112 -0
  57. package/dist/commands/run.js +147 -0
  58. package/dist/commands/save-context.js +171 -0
  59. package/dist/commands/sessions.js +129 -0
  60. package/dist/commands/status.js +138 -0
  61. package/dist/commands/suite/create.js +212 -0
  62. package/dist/commands/suite/delete.js +64 -0
  63. package/dist/commands/suite/export.js +126 -0
  64. package/dist/commands/suite/import.js +128 -0
  65. package/dist/commands/suite/index.js +94 -0
  66. package/dist/commands/suite/list.js +49 -0
  67. package/dist/commands/suite/use.js +227 -0
  68. package/dist/commands/sync.js +117 -0
  69. package/dist/commands/update.js +203 -0
  70. package/dist/mcp/install.js +550 -0
  71. package/dist/mcp/server.js +398 -0
  72. package/dist/mcp/tools.js +922 -0
  73. package/dist/pi-extensions/permission-sync.ts.template +41 -0
  74. package/dist/pi-extensions/permissions-gate.ts.template +69 -0
  75. package/dist/pi-extensions/pr-fix-trigger.ts.template +124 -0
  76. package/dist/pi-extensions/review-reminder.ts.template +58 -0
  77. package/dist/pi-extensions/workspace-input-status.ts.template +919 -0
  78. package/dist/program.js +136 -0
  79. package/dist/scripts/workspace-table.py +394 -0
  80. package/dist/types/dashboard.js +11 -0
  81. package/dist/types/index.js +2 -0
  82. package/dist/utils/agent-config.js +263 -0
  83. package/dist/utils/agent-state.js +178 -0
  84. package/dist/utils/banner.js +24 -0
  85. package/dist/utils/branch-operations.js +232 -0
  86. package/dist/utils/bug-report.js +355 -0
  87. package/dist/utils/cache.js +168 -0
  88. package/dist/utils/claude-integration.js +617 -0
  89. package/dist/utils/claude-sessions.js +260 -0
  90. package/dist/utils/cleanup-operations.js +120 -0
  91. package/dist/utils/colors.js +26 -0
  92. package/dist/utils/command-helpers.js +44 -0
  93. package/dist/utils/config.js +150 -0
  94. package/dist/utils/context-file.js +33 -0
  95. package/dist/utils/dashboard-hooks.js +172 -0
  96. package/dist/utils/dependency-analyzer.js +234 -0
  97. package/dist/utils/diff-operations.js +80 -0
  98. package/dist/utils/doc-generator.js +184 -0
  99. package/dist/utils/ghq-integration.js +332 -0
  100. package/dist/utils/git-operations.js +237 -0
  101. package/dist/utils/git-status.js +187 -0
  102. package/dist/utils/github.js +68 -0
  103. package/dist/utils/health-checks.js +345 -0
  104. package/dist/utils/history.js +150 -0
  105. package/dist/utils/hooks.js +100 -0
  106. package/dist/utils/logger.js +40 -0
  107. package/dist/utils/permission-sync.js +681 -0
  108. package/dist/utils/pi-extensions.js +224 -0
  109. package/dist/utils/progress.js +92 -0
  110. package/dist/utils/prompts.js +279 -0
  111. package/dist/utils/repo-resolver.js +260 -0
  112. package/dist/utils/retry.js +65 -0
  113. package/dist/utils/run-operations.js +30 -0
  114. package/dist/utils/suite.js +159 -0
  115. package/dist/utils/sync-operations.js +107 -0
  116. package/dist/utils/tmux-dashboard.js +184 -0
  117. package/dist/utils/validation.js +153 -0
  118. package/dist/utils/version-check.js +121 -0
  119. package/dist/utils/workspace-meta.js +185 -0
  120. package/install-shell-integration.sh +532 -0
  121. package/install.sh +401 -0
  122. package/package.json +90 -0
  123. package/remove-shell-block.awk +44 -0
  124. package/scripts/postinstall.js +85 -0
  125. package/skills/analyze-deps.md +23 -0
  126. package/skills/archive-workspace.md +26 -0
  127. package/skills/branch-create.md +18 -0
  128. package/skills/create-workspace.md +39 -0
  129. package/skills/delete-workspace.md +37 -0
  130. package/skills/list-org-repos.md +26 -0
  131. package/skills/list-suites.md +24 -0
  132. package/skills/list-workspaces.md +27 -0
  133. package/skills/refresh-workspace-docs.md +89 -0
  134. package/skills/remove-repo.md +25 -0
  135. package/skills/run-command.md +23 -0
  136. package/skills/save-context.md +68 -0
  137. package/skills/search-repos.md +31 -0
  138. package/skills/snapshot-list.md +18 -0
  139. package/skills/snapshot-save.md +12 -0
  140. package/skills/switch-branch.md +18 -0
  141. package/skills/update-cache.md +21 -0
  142. package/skills/update-workspace.md +66 -0
  143. package/skills/workspace-cleanup.md +18 -0
  144. package/skills/workspace-diff.md +23 -0
  145. package/skills/workspace-doctor.md +23 -0
  146. package/skills/workspace-info.md +45 -0
  147. package/skills/workspace-manager/SKILL.md +201 -0
  148. package/skills/workspace-manager/references/ai-prompt.md +44 -0
  149. package/skills/workspace-manager/references/analyze-deps.md +45 -0
  150. package/skills/workspace-manager/references/archive-workspace.md +38 -0
  151. package/skills/workspace-manager/references/branch-create.md +31 -0
  152. package/skills/workspace-manager/references/branch-merge.md +22 -0
  153. package/skills/workspace-manager/references/branch-rebase.md +22 -0
  154. package/skills/workspace-manager/references/cache-clear.md +18 -0
  155. package/skills/workspace-manager/references/cache-info.md +22 -0
  156. package/skills/workspace-manager/references/configure-claude.md +31 -0
  157. package/skills/workspace-manager/references/configure.md +35 -0
  158. package/skills/workspace-manager/references/create-workspace.md +45 -0
  159. package/skills/workspace-manager/references/dashboard.md +61 -0
  160. package/skills/workspace-manager/references/delete-workspace.md +33 -0
  161. package/skills/workspace-manager/references/generate-docs.md +36 -0
  162. package/skills/workspace-manager/references/ghq-status.md +28 -0
  163. package/skills/workspace-manager/references/go.md +33 -0
  164. package/skills/workspace-manager/references/history.md +41 -0
  165. package/skills/workspace-manager/references/list-org-repos.md +26 -0
  166. package/skills/workspace-manager/references/list-suites.md +21 -0
  167. package/skills/workspace-manager/references/list-workspaces.md +27 -0
  168. package/skills/workspace-manager/references/mcp.md +49 -0
  169. package/skills/workspace-manager/references/remove-repo.md +26 -0
  170. package/skills/workspace-manager/references/run-command.md +44 -0
  171. package/skills/workspace-manager/references/search-repos.md +24 -0
  172. package/skills/workspace-manager/references/sessions.md +27 -0
  173. package/skills/workspace-manager/references/suite-create.md +22 -0
  174. package/skills/workspace-manager/references/suite-delete.md +21 -0
  175. package/skills/workspace-manager/references/suite-export.md +19 -0
  176. package/skills/workspace-manager/references/suite-import.md +19 -0
  177. package/skills/workspace-manager/references/suite-use.md +27 -0
  178. package/skills/workspace-manager/references/switch-branch.md +25 -0
  179. package/skills/workspace-manager/references/update-cache.md +22 -0
  180. package/skills/workspace-manager/references/update-workspace.md +50 -0
  181. package/skills/workspace-manager/references/workspace-cleanup.md +33 -0
  182. package/skills/workspace-manager/references/workspace-diff.md +27 -0
  183. package/skills/workspace-manager/references/workspace-doctor.md +27 -0
  184. package/skills/workspace-manager/references/workspace-info.md +33 -0
  185. package/skills/workspace-manager/references/workspace-status.md +43 -0
  186. package/skills/workspace-manager/references/workspace-sync.md +26 -0
  187. package/skills/workspace-status.md +26 -0
  188. package/skills/workspace-sync.md +23 -0
  189. package/src/cli/ai-prompt.test.ts +667 -0
  190. package/src/cli/ai-prompt.ts +421 -0
  191. package/src/cli/dashboard/AgentList.tsx +44 -0
  192. package/src/cli/dashboard/DashboardSidebar.tsx +291 -0
  193. package/src/cli/dashboard/HelpFooter.tsx +22 -0
  194. package/src/cli/dashboard/StatusBadge.tsx +23 -0
  195. package/src/cli/dashboard/index.ts +5 -0
  196. package/src/cli/dashboard/types.ts +16 -0
  197. package/src/cli/tui.tsx +51 -0
  198. package/src/cli/workspace-cli.test.ts +30 -0
  199. package/src/commands/ai-prompt.ts +21 -0
  200. package/src/commands/analyze-deps.ts +119 -0
  201. package/src/commands/archive.ts +114 -0
  202. package/src/commands/branch/create.ts +94 -0
  203. package/src/commands/branch/index.ts +74 -0
  204. package/src/commands/branch/merge.ts +61 -0
  205. package/src/commands/branch/rebase.ts +54 -0
  206. package/src/commands/branch/switch.ts +150 -0
  207. package/src/commands/cache/index.ts +51 -0
  208. package/src/commands/cache/manager.ts +168 -0
  209. package/src/commands/cleanup.ts +132 -0
  210. package/src/commands/configure-claude.ts +55 -0
  211. package/src/commands/configure.ts +204 -0
  212. package/src/commands/create.ts +196 -0
  213. package/src/commands/dashboard/hook-handler.ts +138 -0
  214. package/src/commands/dashboard/index.ts +248 -0
  215. package/src/commands/dashboard/launcher.ts +42 -0
  216. package/src/commands/dashboard/session-picker.ts +73 -0
  217. package/src/commands/dashboard/sidebar.js +39 -0
  218. package/src/commands/dashboard/workspace-picker.ts +62 -0
  219. package/src/commands/delete.test.ts +250 -0
  220. package/src/commands/delete.ts +171 -0
  221. package/src/commands/deprecated-aliases.ts +55 -0
  222. package/src/commands/diff.ts +106 -0
  223. package/src/commands/doctor.ts +110 -0
  224. package/src/commands/generate-docs.ts +45 -0
  225. package/src/commands/ghq-status.ts +70 -0
  226. package/src/commands/go.ts +125 -0
  227. package/src/commands/history.ts +136 -0
  228. package/src/commands/list.test.ts +219 -0
  229. package/src/commands/list.ts +176 -0
  230. package/src/commands/mcp/index.ts +39 -0
  231. package/src/commands/migrate.ts +277 -0
  232. package/src/commands/remove-repo.ts +194 -0
  233. package/src/commands/report-bug.ts +88 -0
  234. package/src/commands/run.ts +125 -0
  235. package/src/commands/save-context.test.ts +113 -0
  236. package/src/commands/save-context.ts +141 -0
  237. package/src/commands/sessions.ts +99 -0
  238. package/src/commands/status.ts +111 -0
  239. package/src/commands/suite/create.ts +240 -0
  240. package/src/commands/suite/delete.ts +65 -0
  241. package/src/commands/suite/export.ts +97 -0
  242. package/src/commands/suite/import.ts +100 -0
  243. package/src/commands/suite/index.ts +66 -0
  244. package/src/commands/suite/list.ts +54 -0
  245. package/src/commands/suite/use.ts +232 -0
  246. package/src/commands/sync.ts +97 -0
  247. package/src/commands/update.ts +197 -0
  248. package/src/mcp/install.ts +529 -0
  249. package/src/mcp/server.ts +579 -0
  250. package/src/mcp/tools.test.ts +545 -0
  251. package/src/mcp/tools.ts +1019 -0
  252. package/src/mcp/update-workspace.test.ts +220 -0
  253. package/src/pi-extensions/permission-sync.ts.template +41 -0
  254. package/src/pi-extensions/permissions-gate.ts.template +69 -0
  255. package/src/pi-extensions/pr-fix-trigger.ts.template +124 -0
  256. package/src/pi-extensions/review-reminder.ts.template +58 -0
  257. package/src/pi-extensions/workspace-input-status.ts.template +919 -0
  258. package/src/program.ts +112 -0
  259. package/src/scripts/workspace-table.py +394 -0
  260. package/src/types/dashboard.ts +29 -0
  261. package/src/types/index.ts +109 -0
  262. package/src/types/ink.d.ts +171 -0
  263. package/src/utils/agent-config.test.ts +303 -0
  264. package/src/utils/agent-config.ts +264 -0
  265. package/src/utils/agent-state.test.ts +166 -0
  266. package/src/utils/agent-state.ts +135 -0
  267. package/src/utils/banner.ts +24 -0
  268. package/src/utils/branch-operations.ts +237 -0
  269. package/src/utils/bug-report.test.ts +174 -0
  270. package/src/utils/bug-report.ts +355 -0
  271. package/src/utils/cache.ts +150 -0
  272. package/src/utils/claude-integration.test.ts +381 -0
  273. package/src/utils/claude-integration.ts +649 -0
  274. package/src/utils/claude-sessions.test.ts +262 -0
  275. package/src/utils/claude-sessions.ts +251 -0
  276. package/src/utils/cleanup-operations.ts +94 -0
  277. package/src/utils/colors.ts +25 -0
  278. package/src/utils/command-helpers.ts +46 -0
  279. package/src/utils/config.test.ts +203 -0
  280. package/src/utils/config.ts +133 -0
  281. package/src/utils/context-file.ts +36 -0
  282. package/src/utils/dashboard-hooks.test.ts +168 -0
  283. package/src/utils/dashboard-hooks.ts +163 -0
  284. package/src/utils/dependency-analyzer.ts +243 -0
  285. package/src/utils/diff-operations.ts +59 -0
  286. package/src/utils/doc-generator.ts +187 -0
  287. package/src/utils/ghq-integration.test.ts +195 -0
  288. package/src/utils/ghq-integration.ts +317 -0
  289. package/src/utils/git-operations.test.ts +265 -0
  290. package/src/utils/git-operations.ts +233 -0
  291. package/src/utils/git-status.ts +171 -0
  292. package/src/utils/github.ts +74 -0
  293. package/src/utils/health-checks.ts +359 -0
  294. package/src/utils/history.ts +140 -0
  295. package/src/utils/hooks.ts +92 -0
  296. package/src/utils/logger.ts +37 -0
  297. package/src/utils/permission-sync.test.ts +593 -0
  298. package/src/utils/permission-sync.ts +720 -0
  299. package/src/utils/pi-extensions.test.ts +182 -0
  300. package/src/utils/pi-extensions.ts +226 -0
  301. package/src/utils/progress.ts +73 -0
  302. package/src/utils/prompts.test.ts +169 -0
  303. package/src/utils/prompts.ts +296 -0
  304. package/src/utils/repo-resolver.test.ts +313 -0
  305. package/src/utils/repo-resolver.ts +294 -0
  306. package/src/utils/retry.test.ts +124 -0
  307. package/src/utils/retry.ts +85 -0
  308. package/src/utils/run-operations.ts +38 -0
  309. package/src/utils/suite.test.ts +256 -0
  310. package/src/utils/suite.ts +144 -0
  311. package/src/utils/sync-operations.ts +87 -0
  312. package/src/utils/tmux-dashboard.ts +193 -0
  313. package/src/utils/validation.test.ts +139 -0
  314. package/src/utils/validation.ts +128 -0
  315. package/src/utils/version-check.test.ts +108 -0
  316. package/src/utils/version-check.ts +96 -0
  317. package/src/utils/workspace-meta.ts +173 -0
  318. package/sync-permissions.sh +137 -0
  319. package/tsconfig.json +20 -0
  320. package/uninstall.sh +162 -0
@@ -0,0 +1,922 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.handleListWorkspaces = handleListWorkspaces;
40
+ exports.handleArchiveWorkspace = handleArchiveWorkspace;
41
+ exports.handleUnarchiveWorkspace = handleUnarchiveWorkspace;
42
+ exports.handleWorkspaceStatus = handleWorkspaceStatus;
43
+ exports.handleWorkspaceDiff = handleWorkspaceDiff;
44
+ exports.handleWorkspaceSync = handleWorkspaceSync;
45
+ exports.handleRunCommand = handleRunCommand;
46
+ exports.handleListSuites = handleListSuites;
47
+ exports.handleWorkspaceInfo = handleWorkspaceInfo;
48
+ exports.handleUpdateWorkspace = handleUpdateWorkspace;
49
+ exports.handleUpdateCache = handleUpdateCache;
50
+ exports.handleCreateWorkspace = handleCreateWorkspace;
51
+ exports.handleSearchRepos = handleSearchRepos;
52
+ exports.handleListOrgRepos = handleListOrgRepos;
53
+ exports.handleDeleteWorkspace = handleDeleteWorkspace;
54
+ exports.handleRemoveRepo = handleRemoveRepo;
55
+ exports.handleWorkspaceDoctor = handleWorkspaceDoctor;
56
+ exports.handleAnalyzeDeps = handleAnalyzeDeps;
57
+ exports.handleBranchCreate = handleBranchCreate;
58
+ exports.handleSwitchBranch = handleSwitchBranch;
59
+ exports.handleWorkspaceCleanup = handleWorkspaceCleanup;
60
+ exports.handleBranchMerge = handleBranchMerge;
61
+ exports.handleBranchRebase = handleBranchRebase;
62
+ exports.handleSuiteCreate = handleSuiteCreate;
63
+ exports.handleSuiteDelete = handleSuiteDelete;
64
+ exports.handleSuiteExport = handleSuiteExport;
65
+ exports.handleSuiteImport = handleSuiteImport;
66
+ exports.handleSuiteUse = handleSuiteUse;
67
+ exports.handleSaveContext = handleSaveContext;
68
+ const fs = __importStar(require("fs/promises"));
69
+ const path = __importStar(require("path"));
70
+ const os = __importStar(require("os"));
71
+ const fuzzy_1 = __importDefault(require("fuzzy"));
72
+ const repo_resolver_1 = require("../utils/repo-resolver");
73
+ const config_1 = require("../utils/config");
74
+ const context_file_1 = require("../utils/context-file");
75
+ const workspace_meta_1 = require("../utils/workspace-meta");
76
+ const git_status_1 = require("../utils/git-status");
77
+ const diff_operations_1 = require("../utils/diff-operations");
78
+ const sync_operations_1 = require("../utils/sync-operations");
79
+ const run_operations_1 = require("../utils/run-operations");
80
+ const suite_1 = require("../utils/suite");
81
+ const github_1 = require("../utils/github");
82
+ const git_operations_1 = require("../utils/git-operations");
83
+ const claude_integration_1 = require("../utils/claude-integration");
84
+ const health_checks_1 = require("../utils/health-checks");
85
+ const dependency_analyzer_1 = require("../utils/dependency-analyzer");
86
+ const branch_operations_1 = require("../utils/branch-operations");
87
+ const cleanup_operations_1 = require("../utils/cleanup-operations");
88
+ const hooks_1 = require("../utils/hooks");
89
+ const validation_1 = require("../utils/validation");
90
+ /**
91
+ * Redirects stdout to stderr for the duration of a function call.
92
+ * MCP uses stdout exclusively for JSON-RPC, so any console.log from
93
+ * utility functions (logInfo, logSuccess, etc.) would corrupt the protocol.
94
+ */
95
+ async function withStdoutProtection(fn) {
96
+ const originalWrite = process.stdout.write;
97
+ process.stdout.write = process.stderr.write.bind(process.stderr);
98
+ try {
99
+ return await fn();
100
+ }
101
+ finally {
102
+ process.stdout.write = originalWrite;
103
+ }
104
+ }
105
+ async function handleListWorkspaces(includeArchived = false) {
106
+ return withStdoutProtection(async () => {
107
+ const workspaces = await (0, workspace_meta_1.listWorkspaces)(includeArchived);
108
+ return workspaces.map(ws => ({
109
+ name: ws.name,
110
+ path: ws.path,
111
+ repoCount: ws.metadata?.repositories.filter(r => r.status === 'success').length ?? 0,
112
+ createdAt: ws.metadata?.createdAt ?? null,
113
+ archivedAt: ws.metadata?.archivedAt ?? null,
114
+ }));
115
+ });
116
+ }
117
+ async function handleArchiveWorkspace(workspace) {
118
+ return withStdoutProtection(async () => {
119
+ await (0, workspace_meta_1.archiveWorkspace)(workspace);
120
+ return {
121
+ workspace,
122
+ status: 'archived',
123
+ message: `Workspace "${workspace}" has been archived. It will be auto-deleted in 30 days.`,
124
+ };
125
+ });
126
+ }
127
+ async function handleUnarchiveWorkspace(workspace) {
128
+ return withStdoutProtection(async () => {
129
+ await (0, workspace_meta_1.unarchiveWorkspace)(workspace);
130
+ return {
131
+ workspace,
132
+ status: 'active',
133
+ message: `Workspace "${workspace}" has been unarchived and is now active.`,
134
+ };
135
+ });
136
+ }
137
+ async function handleWorkspaceStatus(workspace) {
138
+ return withStdoutProtection(async () => {
139
+ const workspacePath = (0, validation_1.safeWorkspacePath)(workspace);
140
+ const metadata = await (0, workspace_meta_1.loadMetadata)(workspacePath);
141
+ if (!metadata) {
142
+ throw new Error(`Workspace not found: ${workspace}`);
143
+ }
144
+ const repos = metadata.repositories.filter(r => r.status === 'success');
145
+ const repoNames = repos.map(r => r.directoryName);
146
+ const statuses = await (0, git_status_1.getAllReposStatus)(workspacePath, repoNames);
147
+ return {
148
+ workspace,
149
+ repos: statuses,
150
+ };
151
+ });
152
+ }
153
+ async function handleWorkspaceDiff(workspace, full = false) {
154
+ return withStdoutProtection(async () => {
155
+ const workspacePath = (0, validation_1.safeWorkspacePath)(workspace);
156
+ const metadata = await (0, workspace_meta_1.loadMetadata)(workspacePath);
157
+ if (!metadata) {
158
+ throw new Error(`Workspace not found: ${workspace}`);
159
+ }
160
+ const repos = metadata.repositories.filter(r => r.status === 'success');
161
+ const diffs = [];
162
+ for (const repo of repos) {
163
+ const repoPath = path.join(workspacePath, repo.directoryName);
164
+ const diff = await (0, diff_operations_1.getRepoDiff)(repoPath, repo.directoryName);
165
+ diffs.push(diff);
166
+ }
167
+ const totalStaged = diffs.reduce((sum, d) => sum + d.staged.files, 0);
168
+ const totalUnstaged = diffs.reduce((sum, d) => sum + d.unstaged.files, 0);
169
+ const reposWithChanges = diffs.filter(d => d.staged.files > 0 || d.unstaged.files > 0).length;
170
+ return {
171
+ workspace,
172
+ diffs,
173
+ summary: { totalStaged, totalUnstaged, reposWithChanges },
174
+ };
175
+ });
176
+ }
177
+ async function handleWorkspaceSync(workspace) {
178
+ return withStdoutProtection(async () => {
179
+ const workspacePath = (0, validation_1.safeWorkspacePath)(workspace);
180
+ const metadata = await (0, workspace_meta_1.loadMetadata)(workspacePath);
181
+ if (!metadata) {
182
+ throw new Error(`Workspace not found: ${workspace}`);
183
+ }
184
+ const repos = metadata.repositories.filter(r => r.status === 'success');
185
+ const results = [];
186
+ for (const repo of repos) {
187
+ const repoPath = path.join(workspacePath, repo.directoryName);
188
+ const result = await (0, sync_operations_1.syncRepository)(repoPath, repo.directoryName, true);
189
+ results.push(result);
190
+ }
191
+ const successful = results.filter(r => r.status === 'success').length;
192
+ const failed = results.filter(r => r.status === 'failed').length;
193
+ const skipped = results.filter(r => r.status === 'skipped').length;
194
+ return {
195
+ workspace,
196
+ results,
197
+ summary: { successful, failed, skipped },
198
+ };
199
+ });
200
+ }
201
+ async function handleRunCommand(workspace, command) {
202
+ return withStdoutProtection(async () => {
203
+ const workspacePath = (0, validation_1.safeWorkspacePath)(workspace);
204
+ const metadata = await (0, workspace_meta_1.loadMetadata)(workspacePath);
205
+ if (!metadata) {
206
+ throw new Error(`Workspace not found: ${workspace}`);
207
+ }
208
+ const repos = metadata.repositories.filter(r => r.status === 'success');
209
+ const results = [];
210
+ const concurrency = 3;
211
+ for (let i = 0; i < repos.length; i += concurrency) {
212
+ const chunk = repos.slice(i, i + concurrency);
213
+ const chunkResults = await Promise.all(chunk.map(repo => {
214
+ const repoPath = path.join(workspacePath, repo.directoryName);
215
+ return (0, run_operations_1.runInRepo)(repoPath, repo.directoryName, command);
216
+ }));
217
+ results.push(...chunkResults);
218
+ }
219
+ const successful = results.filter(r => r.status === 'success').length;
220
+ const failed = results.filter(r => r.status === 'failed').length;
221
+ return {
222
+ workspace,
223
+ command,
224
+ results,
225
+ summary: { successful, failed },
226
+ };
227
+ });
228
+ }
229
+ async function handleListSuites() {
230
+ return withStdoutProtection(async () => {
231
+ const suites = await (0, suite_1.listSuites)();
232
+ return suites.map(s => ({
233
+ name: s.name,
234
+ description: s.description,
235
+ repoCount: s.entries.length,
236
+ hasHooks: (s.postCloneHooks?.length ?? 0) > 0,
237
+ createdAt: s.createdAt,
238
+ updatedAt: s.updatedAt,
239
+ }));
240
+ });
241
+ }
242
+ async function handleWorkspaceInfo(workspace) {
243
+ return withStdoutProtection(async () => {
244
+ const workspacePath = (0, validation_1.safeWorkspacePath)(workspace);
245
+ const metadata = await (0, workspace_meta_1.loadMetadata)(workspacePath);
246
+ if (!metadata) {
247
+ throw new Error(`Workspace not found: ${workspace}`);
248
+ }
249
+ return {
250
+ ...metadata,
251
+ path: workspacePath,
252
+ };
253
+ });
254
+ }
255
+ async function handleUpdateWorkspace(workspace, repos) {
256
+ return withStdoutProtection(async () => {
257
+ if (!workspace || workspace.trim().length === 0) {
258
+ throw new Error('Workspace name is required');
259
+ }
260
+ if (!repos || repos.length === 0) {
261
+ throw new Error('At least one repository name is required');
262
+ }
263
+ const workspacePath = (0, validation_1.safeWorkspacePath)(workspace);
264
+ const metadata = await (0, workspace_meta_1.loadMetadata)(workspacePath);
265
+ if (!metadata) {
266
+ throw new Error(`Workspace not found: ${workspace}`);
267
+ }
268
+ const existingDirectoryNames = metadata.repositories.map(r => r.directoryName);
269
+ // Fetch all repos to resolve names to full repo objects
270
+ const allRepos = await (0, github_1.fetchOrgRepos)();
271
+ const repoEntries = [];
272
+ const notFound = [];
273
+ const alreadyExists = [];
274
+ const fuzzyMatched = [];
275
+ for (const repoInput of repos) {
276
+ const repoName = typeof repoInput === 'string' ? repoInput : repoInput.name;
277
+ const suffix = typeof repoInput === 'string' ? undefined : repoInput.suffix;
278
+ const { resolved, notFound: missing } = (0, repo_resolver_1.resolveRepoNames)([repoName], allRepos);
279
+ if (missing.length > 0) {
280
+ notFound.push(repoName);
281
+ continue;
282
+ }
283
+ const found = resolved[0].repo;
284
+ if (!resolved[0].exact) {
285
+ fuzzyMatched.push({ requested: repoName, resolved: found.name });
286
+ }
287
+ if (suffix && !/^[a-zA-Z0-9_-]+$/.test(suffix)) {
288
+ throw new Error(`Invalid suffix "${suffix}" for repo "${repoName}". Only alphanumeric characters, hyphens, and underscores are allowed.`);
289
+ }
290
+ const directoryName = suffix ? `${found.name}-${suffix}` : found.name;
291
+ if (existingDirectoryNames.includes(directoryName)) {
292
+ alreadyExists.push(suffix ? `${repoName} (as ${directoryName})` : repoName);
293
+ continue;
294
+ }
295
+ existingDirectoryNames.push(directoryName);
296
+ repoEntries.push({ repo: found, directoryName });
297
+ }
298
+ if (repoEntries.length === 0) {
299
+ const reasons = [];
300
+ if (notFound.length > 0)
301
+ reasons.push(`not found: ${notFound.join(', ')}`);
302
+ if (alreadyExists.length > 0)
303
+ reasons.push(`already in workspace: ${alreadyExists.join(', ')}`);
304
+ throw new Error(`No new repos to add (${reasons.join('; ')})`);
305
+ }
306
+ // Clone new repositories
307
+ const cloneResults = await (0, git_operations_1.cloneRepositories)(repoEntries, workspacePath);
308
+ // Update metadata
309
+ const newRepoMetadata = cloneResults.map(result => ({
310
+ name: result.repo.name,
311
+ directoryName: result.directoryName,
312
+ owner: result.repo.owner.login,
313
+ clonedAt: result.clonedAt || new Date().toISOString(),
314
+ cloneUrl: (0, config_1.getCloneUrl)(result.repo),
315
+ status: result.status,
316
+ error: result.error,
317
+ }));
318
+ metadata.repositories.push(...newRepoMetadata);
319
+ metadata.lastModified = new Date().toISOString();
320
+ await (0, workspace_meta_1.saveMetadata)(workspacePath, metadata);
321
+ const successful = cloneResults.filter(r => r.status === 'success');
322
+ const failed = cloneResults.filter(r => r.status === 'failed');
323
+ // Regenerate the agent context (CLAUDE.md/AGENTS.md) + .mcp.json so the
324
+ // newly added repos surface — otherwise an investigate-first workspace's
325
+ // context keeps saying "0 repositories". Resolve the FULL set (existing +
326
+ // new) from the catalog so pre-existing repos keep their descriptions/URLs
327
+ // on regenerate (matches the remove-repo path). Best-effort.
328
+ const contextRepoObjects = metadata.repositories
329
+ .filter(r => r.status === 'success')
330
+ .map(r => allRepos.find(ar => ar.name === r.name))
331
+ .filter((r) => r != null);
332
+ // Only regenerate when this update actually added a repo — a failure-only
333
+ // update (all new clones failed) must not rewrite the existing context.
334
+ try {
335
+ if (successful.length > 0 && contextRepoObjects.length > 0) {
336
+ await (0, claude_integration_1.generateClaudeContext)(workspacePath, workspace, contextRepoObjects, metadata);
337
+ }
338
+ }
339
+ catch { /* context regen is best-effort */ }
340
+ return {
341
+ workspace,
342
+ path: workspacePath,
343
+ added: cloneResults.map(r => ({
344
+ name: r.repo.name,
345
+ directoryName: r.directoryName,
346
+ status: r.status,
347
+ error: r.error,
348
+ })),
349
+ summary: {
350
+ successful: successful.length,
351
+ failed: failed.length,
352
+ totalRepos: metadata.repositories.filter(r => r.status === 'success').length,
353
+ },
354
+ alreadyExists: alreadyExists.length > 0 ? alreadyExists : undefined,
355
+ notFound: notFound.length > 0 ? notFound : undefined,
356
+ fuzzyMatched: fuzzyMatched.length > 0 ? fuzzyMatched : undefined,
357
+ };
358
+ });
359
+ }
360
+ async function handleUpdateCache() {
361
+ return withStdoutProtection(async () => {
362
+ const repos = await (0, github_1.fetchOrgRepos)({ forceRefresh: true });
363
+ return {
364
+ repos: repos.length,
365
+ message: `Cache updated with ${repos.length} repositories`,
366
+ };
367
+ });
368
+ }
369
+ async function handleCreateWorkspace(workspace, repos) {
370
+ return withStdoutProtection(async () => {
371
+ if (!workspace || workspace.trim().length === 0) {
372
+ throw new Error('Workspace name is required');
373
+ }
374
+ if (!repos || repos.length === 0) {
375
+ throw new Error('At least one repository name is required');
376
+ }
377
+ const sanitizedWorkspace = (0, validation_1.sanitizeWorkspaceName)(workspace);
378
+ const workspacePath = (0, validation_1.safeWorkspacePath)(sanitizedWorkspace);
379
+ // Auto-resolve workspace name conflict instead of throwing
380
+ let finalWorkspaceName = sanitizedWorkspace;
381
+ let wsExists = false;
382
+ try {
383
+ await fs.access(workspacePath);
384
+ wsExists = true;
385
+ }
386
+ catch {
387
+ // Directory doesn't exist — good, use the original name
388
+ }
389
+ if (wsExists) {
390
+ // Workspace already exists — resolve to a unique name using repo names as hints
391
+ // (errors from resolveWorkspaceNameConflict intentionally propagate)
392
+ finalWorkspaceName = await (0, validation_1.resolveWorkspaceNameConflict)(sanitizedWorkspace, repos);
393
+ }
394
+ // Fetch all repos to resolve names to full repo objects
395
+ const allRepos = await (0, github_1.fetchOrgRepos)();
396
+ const repoEntries = [];
397
+ const notFound = [];
398
+ const { resolved, notFound: missing } = (0, repo_resolver_1.resolveRepoNames)(repos, allRepos);
399
+ notFound.push(...missing);
400
+ const fuzzyMatched = [];
401
+ for (const r of resolved) {
402
+ repoEntries.push({ repo: r.repo, directoryName: r.repo.name });
403
+ if (!r.exact)
404
+ fuzzyMatched.push({ requested: r.input, resolved: r.repo.name });
405
+ }
406
+ if (repoEntries.length === 0) {
407
+ throw new Error(`None of the specified repos were found: ${notFound.join(', ')}`);
408
+ }
409
+ // Create workspace directory
410
+ const finalWorkspacePath = (0, validation_1.safeWorkspacePath)(finalWorkspaceName);
411
+ await fs.mkdir(finalWorkspacePath, { recursive: true });
412
+ // Clone repositories
413
+ const cloneResults = await (0, git_operations_1.cloneRepositories)(repoEntries, finalWorkspacePath);
414
+ // Save metadata
415
+ const metadata = (0, workspace_meta_1.createMetadata)(finalWorkspaceName, cloneResults);
416
+ await (0, workspace_meta_1.saveMetadata)(finalWorkspacePath, metadata);
417
+ // Generate Claude context
418
+ const successfulRepos = cloneResults
419
+ .filter(r => r.status === 'success')
420
+ .map(r => r.repo);
421
+ if (successfulRepos.length > 0) {
422
+ await (0, claude_integration_1.generateClaudeContext)(finalWorkspacePath, finalWorkspaceName, successfulRepos, metadata);
423
+ }
424
+ const successful = cloneResults.filter(r => r.status === 'success');
425
+ const failed = cloneResults.filter(r => r.status === 'failed');
426
+ // Write workspace path to temp file so shell integration can cd to it
427
+ if (successful.length > 0) {
428
+ const tempFile = path.join(os.homedir(), '.workspace-last-created');
429
+ await fs.writeFile(tempFile, finalWorkspacePath, 'utf-8').catch(() => { });
430
+ }
431
+ return {
432
+ workspace: finalWorkspaceName,
433
+ originalWorkspaceName: finalWorkspaceName !== sanitizedWorkspace ? sanitizedWorkspace : undefined,
434
+ path: finalWorkspacePath,
435
+ repos: cloneResults.map(r => ({
436
+ name: r.repo.name,
437
+ directoryName: r.directoryName,
438
+ status: r.status,
439
+ error: r.error,
440
+ })),
441
+ summary: {
442
+ successful: successful.length,
443
+ failed: failed.length,
444
+ },
445
+ notFound: notFound.length > 0 ? notFound : undefined,
446
+ fuzzyMatched: fuzzyMatched.length > 0 ? fuzzyMatched : undefined,
447
+ };
448
+ });
449
+ }
450
+ async function handleSearchRepos(query, limit = 20) {
451
+ return withStdoutProtection(async () => {
452
+ const allRepos = await (0, github_1.fetchOrgRepos)();
453
+ const results = fuzzy_1.default.filter(query, allRepos, {
454
+ extract: (repo) => `${repo.name} ${repo.description || ''}`,
455
+ });
456
+ return results.slice(0, limit).map(r => ({
457
+ name: r.original.name,
458
+ description: r.original.description,
459
+ url: r.original.url,
460
+ isPrivate: r.original.isPrivate,
461
+ score: r.score,
462
+ }));
463
+ });
464
+ }
465
+ async function handleListOrgRepos(forceRefresh = false) {
466
+ return withStdoutProtection(async () => {
467
+ const repos = await (0, github_1.fetchOrgRepos)({ forceRefresh });
468
+ return {
469
+ repos: repos.map(r => ({
470
+ name: r.name,
471
+ description: r.description,
472
+ url: r.url,
473
+ isPrivate: r.isPrivate,
474
+ })),
475
+ total: repos.length,
476
+ };
477
+ });
478
+ }
479
+ async function handleDeleteWorkspace(workspaces) {
480
+ return withStdoutProtection(async () => {
481
+ const names = Array.isArray(workspaces) ? workspaces : [workspaces];
482
+ const results = [];
483
+ for (const workspace of names) {
484
+ const workspacePath = (0, validation_1.safeWorkspacePath)(workspace);
485
+ try {
486
+ await fs.access(workspacePath);
487
+ }
488
+ catch {
489
+ results.push({ workspace, path: workspacePath, deleted: false, error: `Workspace directory not found: ${workspace}` });
490
+ continue;
491
+ }
492
+ try {
493
+ await fs.rm(workspacePath, { recursive: true, force: true });
494
+ results.push({ workspace, path: workspacePath, deleted: true });
495
+ }
496
+ catch (err) {
497
+ results.push({ workspace, path: workspacePath, deleted: false, error: err instanceof Error ? err.message : 'Unknown error' });
498
+ }
499
+ }
500
+ if (names.length === 1) {
501
+ const r = results[0];
502
+ if (!r.deleted)
503
+ throw new Error(r.error);
504
+ return { deleted: r.workspace, path: r.path };
505
+ }
506
+ return { results };
507
+ });
508
+ }
509
+ async function handleRemoveRepo(workspace, repo) {
510
+ return withStdoutProtection(async () => {
511
+ const workspacePath = (0, validation_1.safeWorkspacePath)(workspace);
512
+ const metadata = await (0, workspace_meta_1.loadMetadata)(workspacePath);
513
+ if (!metadata) {
514
+ throw new Error(`Workspace not found: ${workspace}`);
515
+ }
516
+ const repoIndex = metadata.repositories.findIndex(r => r.directoryName === repo);
517
+ if (repoIndex === -1) {
518
+ throw new Error(`Repository not found in workspace: ${repo}`);
519
+ }
520
+ const repoPath = path.join(workspacePath, repo);
521
+ await fs.rm(repoPath, { recursive: true, force: true });
522
+ metadata.repositories.splice(repoIndex, 1);
523
+ await (0, workspace_meta_1.saveMetadata)(workspacePath, metadata);
524
+ const successfulRepos = metadata.repositories
525
+ .filter(r => r.status === 'success');
526
+ // Best-effort regeneration of Claude context
527
+ try {
528
+ if (successfulRepos.length > 0) {
529
+ const allRepos = await (0, github_1.fetchOrgRepos)();
530
+ const repoObjects = successfulRepos
531
+ .map(r => allRepos.find(ar => ar.name === r.name))
532
+ .filter((r) => r != null);
533
+ if (repoObjects.length > 0) {
534
+ await (0, claude_integration_1.generateClaudeContext)(workspacePath, workspace, repoObjects, metadata);
535
+ }
536
+ }
537
+ }
538
+ catch {
539
+ // Claude context regeneration is non-critical
540
+ }
541
+ return {
542
+ removed: repo,
543
+ workspace,
544
+ remainingRepos: successfulRepos.length,
545
+ };
546
+ });
547
+ }
548
+ async function handleWorkspaceDoctor(workspace) {
549
+ return withStdoutProtection(async () => {
550
+ const workspacePath = (0, validation_1.safeWorkspacePath)(workspace);
551
+ const metadata = await (0, workspace_meta_1.loadMetadata)(workspacePath);
552
+ if (!metadata) {
553
+ throw new Error(`Workspace not found: ${workspace}`);
554
+ }
555
+ const results = await (0, health_checks_1.runAllHealthChecks)(workspacePath, metadata);
556
+ const score = (0, health_checks_1.calculateHealthScore)(results);
557
+ return {
558
+ workspace,
559
+ score,
560
+ checks: results,
561
+ };
562
+ });
563
+ }
564
+ async function handleAnalyzeDeps(workspace) {
565
+ return withStdoutProtection(async () => {
566
+ const workspacePath = (0, validation_1.safeWorkspacePath)(workspace);
567
+ const metadata = await (0, workspace_meta_1.loadMetadata)(workspacePath);
568
+ if (!metadata) {
569
+ throw new Error(`Workspace not found: ${workspace}`);
570
+ }
571
+ const repos = metadata.repositories.filter(r => r.status === 'success');
572
+ const repoNames = repos.map(r => r.directoryName);
573
+ const analyses = await (0, dependency_analyzer_1.analyzeDependencies)(workspacePath, repoNames);
574
+ const circularDeps = (0, dependency_analyzer_1.detectCircularDependencies)(analyses);
575
+ // Convert Map to plain object for JSON serialization
576
+ const analysesObj = {};
577
+ for (const [key, value] of analyses) {
578
+ analysesObj[key] = value;
579
+ }
580
+ return {
581
+ workspace,
582
+ analyses: analysesObj,
583
+ circularDependencies: circularDeps,
584
+ };
585
+ });
586
+ }
587
+ async function handleBranchCreate(workspace, branchName, baseBranch) {
588
+ return withStdoutProtection(async () => {
589
+ const workspacePath = (0, validation_1.safeWorkspacePath)(workspace);
590
+ const metadata = await (0, workspace_meta_1.loadMetadata)(workspacePath);
591
+ if (!metadata) {
592
+ throw new Error(`Workspace not found: ${workspace}`);
593
+ }
594
+ const repos = metadata.repositories.filter(r => r.status === 'success');
595
+ const results = [];
596
+ for (const repo of repos) {
597
+ const repoPath = path.join(workspacePath, repo.directoryName);
598
+ const result = await (0, branch_operations_1.createBranch)(repoPath, repo.directoryName, branchName, baseBranch);
599
+ results.push(result);
600
+ }
601
+ const successful = results.filter(r => r.success).length;
602
+ const failed = results.filter(r => !r.success).length;
603
+ return {
604
+ workspace,
605
+ branchName,
606
+ baseBranch: baseBranch || 'current',
607
+ results,
608
+ summary: { successful, failed },
609
+ };
610
+ });
611
+ }
612
+ async function handleSwitchBranch(workspace, branch) {
613
+ return withStdoutProtection(async () => {
614
+ const workspacePath = (0, validation_1.safeWorkspacePath)(workspace);
615
+ const metadata = await (0, workspace_meta_1.loadMetadata)(workspacePath);
616
+ if (!metadata) {
617
+ throw new Error(`Workspace not found: ${workspace}`);
618
+ }
619
+ const repos = metadata.repositories.filter(r => r.status === 'success');
620
+ const results = [];
621
+ for (const repo of repos) {
622
+ const repoPath = path.join(workspacePath, repo.directoryName);
623
+ const result = await (0, branch_operations_1.switchBranch)(repoPath, repo.directoryName, branch);
624
+ results.push(result);
625
+ }
626
+ const successful = results.filter(r => r.status === 'success').length;
627
+ const failed = results.filter(r => r.status === 'failed').length;
628
+ const skipped = results.filter(r => r.status === 'skipped').length;
629
+ return {
630
+ workspace,
631
+ branch,
632
+ results,
633
+ summary: { successful, failed, skipped },
634
+ };
635
+ });
636
+ }
637
+ async function handleWorkspaceCleanup(workspace, includeNodeModules = true, includeBuildArtifacts = true) {
638
+ return withStdoutProtection(async () => {
639
+ const workspacePath = (0, validation_1.safeWorkspacePath)(workspace);
640
+ const metadata = await (0, workspace_meta_1.loadMetadata)(workspacePath);
641
+ if (!metadata) {
642
+ throw new Error(`Workspace not found: ${workspace}`);
643
+ }
644
+ const repos = metadata.repositories.filter(r => r.status === 'success');
645
+ const results = [];
646
+ for (const repo of repos) {
647
+ const repoPath = path.join(workspacePath, repo.directoryName);
648
+ const repoResult = { repo: repo.directoryName };
649
+ if (includeNodeModules) {
650
+ repoResult.nodeModules = await (0, cleanup_operations_1.removeNodeModules)(repoPath);
651
+ }
652
+ if (includeBuildArtifacts) {
653
+ repoResult.buildArtifacts = await (0, cleanup_operations_1.removeBuildArtifacts)(repoPath);
654
+ }
655
+ results.push(repoResult);
656
+ }
657
+ // Calculate total space freed
658
+ let totalFreedMB = 0;
659
+ for (const r of results) {
660
+ if (r.nodeModules) {
661
+ totalFreedMB += parseFloat(r.nodeModules.spaceFreed) || 0;
662
+ }
663
+ if (r.buildArtifacts) {
664
+ totalFreedMB += parseFloat(r.buildArtifacts.spaceFreed) || 0;
665
+ }
666
+ }
667
+ return {
668
+ workspace,
669
+ results,
670
+ totalSpaceFreed: `${totalFreedMB.toFixed(2)} MB`,
671
+ };
672
+ });
673
+ }
674
+ // --- Branch merge/rebase ---
675
+ async function handleBranchMerge(workspace, sourceBranch, targetBranch, strategy) {
676
+ return withStdoutProtection(async () => {
677
+ const workspacePath = (0, validation_1.safeWorkspacePath)(workspace);
678
+ const metadata = await (0, workspace_meta_1.loadMetadata)(workspacePath);
679
+ if (!metadata) {
680
+ throw new Error(`Workspace not found: ${workspace}`);
681
+ }
682
+ const repos = metadata.repositories.filter(r => r.status === 'success');
683
+ const results = [];
684
+ const options = strategy ? {
685
+ noFf: strategy === 'no-ff',
686
+ ffOnly: strategy === 'ff-only',
687
+ squash: strategy === 'squash',
688
+ } : undefined;
689
+ for (const repo of repos) {
690
+ const repoPath = path.join(workspacePath, repo.directoryName);
691
+ const result = await (0, branch_operations_1.mergeBranch)(repoPath, repo.directoryName, sourceBranch, targetBranch, options);
692
+ results.push(result);
693
+ }
694
+ const successful = results.filter(r => r.success).length;
695
+ const failed = results.filter(r => !r.success).length;
696
+ return {
697
+ workspace,
698
+ sourceBranch,
699
+ targetBranch,
700
+ strategy: strategy || 'default',
701
+ results,
702
+ summary: { successful, failed },
703
+ };
704
+ });
705
+ }
706
+ async function handleBranchRebase(workspace, targetBranch) {
707
+ return withStdoutProtection(async () => {
708
+ const workspacePath = (0, validation_1.safeWorkspacePath)(workspace);
709
+ const metadata = await (0, workspace_meta_1.loadMetadata)(workspacePath);
710
+ if (!metadata) {
711
+ throw new Error(`Workspace not found: ${workspace}`);
712
+ }
713
+ const repos = metadata.repositories.filter(r => r.status === 'success');
714
+ const results = [];
715
+ for (const repo of repos) {
716
+ const repoPath = path.join(workspacePath, repo.directoryName);
717
+ const result = await (0, branch_operations_1.rebaseBranch)(repoPath, repo.directoryName, targetBranch);
718
+ results.push(result);
719
+ }
720
+ const successful = results.filter(r => r.success).length;
721
+ const failed = results.filter(r => !r.success).length;
722
+ return {
723
+ workspace,
724
+ targetBranch,
725
+ results,
726
+ summary: { successful, failed },
727
+ };
728
+ });
729
+ }
730
+ // --- Suite operations ---
731
+ async function handleSuiteCreate(name, repos, description) {
732
+ return withStdoutProtection(async () => {
733
+ const validation = (0, suite_1.validateSuiteName)(name);
734
+ if (validation !== true) {
735
+ throw new Error(`Invalid suite name: ${validation}`);
736
+ }
737
+ if (!repos || repos.length === 0) {
738
+ throw new Error('At least one repository name is required');
739
+ }
740
+ const existing = await (0, suite_1.getSuite)(name);
741
+ if (existing) {
742
+ throw new Error(`Suite already exists: ${name}. Delete it first or use a different name.`);
743
+ }
744
+ const entries = repos.map(repoName => ({
745
+ repoName,
746
+ directoryName: repoName,
747
+ }));
748
+ const suite = {
749
+ name,
750
+ description: description || '',
751
+ entries,
752
+ createdAt: new Date().toISOString(),
753
+ updatedAt: new Date().toISOString(),
754
+ };
755
+ await (0, suite_1.saveSuite)(suite);
756
+ return {
757
+ name: suite.name,
758
+ description: suite.description,
759
+ repoCount: entries.length,
760
+ repos: repos,
761
+ };
762
+ });
763
+ }
764
+ async function handleSuiteDelete(name) {
765
+ return withStdoutProtection(async () => {
766
+ const deleted = await (0, suite_1.deleteSuite)(name);
767
+ if (!deleted) {
768
+ throw new Error(`Suite not found: ${name}`);
769
+ }
770
+ return { deleted: name };
771
+ });
772
+ }
773
+ async function handleSuiteExport(name) {
774
+ return withStdoutProtection(async () => {
775
+ if (name) {
776
+ const result = await (0, suite_1.exportSuite)(name);
777
+ if (!result) {
778
+ throw new Error(`Suite not found: ${name}`);
779
+ }
780
+ return result;
781
+ }
782
+ return await (0, suite_1.exportAllSuites)();
783
+ });
784
+ }
785
+ async function handleSuiteImport(content, overwrite = false) {
786
+ return withStdoutProtection(async () => {
787
+ let parsed;
788
+ try {
789
+ parsed = JSON.parse(content);
790
+ }
791
+ catch {
792
+ throw new Error('Invalid JSON content');
793
+ }
794
+ if (!parsed.suites || !Array.isArray(parsed.suites)) {
795
+ throw new Error('Invalid suites format: expected { version: 1, suites: [...] }');
796
+ }
797
+ const result = await (0, suite_1.importSuites)(parsed, overwrite);
798
+ return result;
799
+ });
800
+ }
801
+ async function handleSuiteUse(suiteName, workspace) {
802
+ return withStdoutProtection(async () => {
803
+ const suite = await (0, suite_1.getSuite)(suiteName);
804
+ if (!suite) {
805
+ throw new Error(`Suite not found: ${suiteName}`);
806
+ }
807
+ const sanitizedSuiteWorkspace = (0, validation_1.sanitizeWorkspaceName)(workspace);
808
+ const workspacePath = (0, validation_1.safeWorkspacePath)(sanitizedSuiteWorkspace);
809
+ // Auto-resolve workspace name conflict using suite repo names as hints
810
+ let finalSuiteWorkspaceName = sanitizedSuiteWorkspace;
811
+ let suiteWsExists = false;
812
+ try {
813
+ await fs.access(workspacePath);
814
+ suiteWsExists = true;
815
+ }
816
+ catch {
817
+ // Directory doesn't exist — good
818
+ }
819
+ if (suiteWsExists) {
820
+ // Workspace already exists — resolve to a unique name
821
+ // (errors from resolveWorkspaceNameConflict intentionally propagate)
822
+ const suiteRepoNames = suite.entries.map(e => e.repoName);
823
+ finalSuiteWorkspaceName = await (0, validation_1.resolveWorkspaceNameConflict)(sanitizedSuiteWorkspace, suiteRepoNames);
824
+ }
825
+ // Resolve suite entries to GitHub repos
826
+ const allRepos = await (0, github_1.fetchOrgRepos)();
827
+ const repoEntries = [];
828
+ const notFound = [];
829
+ for (const entry of suite.entries) {
830
+ const found = allRepos.find(r => r.name.toLowerCase() === entry.repoName.toLowerCase());
831
+ if (found) {
832
+ repoEntries.push({ repo: found, directoryName: entry.directoryName });
833
+ }
834
+ else {
835
+ notFound.push(entry.repoName);
836
+ }
837
+ }
838
+ if (repoEntries.length === 0) {
839
+ throw new Error(`None of the suite repos were found: ${notFound.join(', ')}`);
840
+ }
841
+ // Create workspace directory
842
+ const finalSuiteWorkspacePath = (0, validation_1.safeWorkspacePath)(finalSuiteWorkspaceName);
843
+ await fs.mkdir(finalSuiteWorkspacePath, { recursive: true });
844
+ // Clone repositories
845
+ const cloneResults = await (0, git_operations_1.cloneRepositories)(repoEntries, finalSuiteWorkspacePath);
846
+ // Save metadata
847
+ const metadata = (0, workspace_meta_1.createMetadata)(finalSuiteWorkspaceName, cloneResults);
848
+ await (0, workspace_meta_1.saveMetadata)(finalSuiteWorkspacePath, metadata);
849
+ // Generate Claude context
850
+ const successfulRepos = cloneResults
851
+ .filter(r => r.status === 'success')
852
+ .map(r => r.repo);
853
+ if (successfulRepos.length > 0) {
854
+ await (0, claude_integration_1.generateClaudeContext)(finalSuiteWorkspacePath, finalSuiteWorkspaceName, successfulRepos, metadata);
855
+ }
856
+ // Run post-clone hooks if suite has them
857
+ if (suite.postCloneHooks && suite.postCloneHooks.length > 0) {
858
+ try {
859
+ const repoMetadata = metadata.repositories.filter(r => r.status === 'success');
860
+ await (0, hooks_1.runPostCloneHooks)(finalSuiteWorkspacePath, repoMetadata, suite.postCloneHooks);
861
+ }
862
+ catch {
863
+ // Post-clone hooks are non-critical
864
+ }
865
+ }
866
+ const successful = cloneResults.filter(r => r.status === 'success');
867
+ const failed = cloneResults.filter(r => r.status === 'failed');
868
+ return {
869
+ workspace: finalSuiteWorkspaceName,
870
+ originalWorkspaceName: finalSuiteWorkspaceName !== sanitizedSuiteWorkspace ? sanitizedSuiteWorkspace : undefined,
871
+ suite: suiteName,
872
+ path: finalSuiteWorkspacePath,
873
+ repos: cloneResults.map(r => ({
874
+ name: r.repo.name,
875
+ directoryName: r.directoryName,
876
+ status: r.status,
877
+ error: r.error,
878
+ })),
879
+ summary: { successful: successful.length, failed: failed.length },
880
+ notFound: notFound.length > 0 ? notFound : undefined,
881
+ };
882
+ });
883
+ }
884
+ async function handleSaveContext(workspace, content, append) {
885
+ return withStdoutProtection(async () => {
886
+ if (!workspace || workspace.trim().length === 0) {
887
+ throw new Error('Workspace name is required');
888
+ }
889
+ if (!content || content.trim().length === 0) {
890
+ throw new Error('Content is required');
891
+ }
892
+ const workspacePath = (0, validation_1.safeWorkspacePath)(workspace);
893
+ const metadata = await (0, workspace_meta_1.loadMetadata)(workspacePath);
894
+ if (!metadata) {
895
+ throw new Error(`Workspace not found: ${workspace}`);
896
+ }
897
+ const contextPath = path.join(workspacePath, 'CONTEXT.md');
898
+ const timestamp = new Date().toISOString().replace('T', ' ').replace(/\.\d+Z$/, 'Z');
899
+ let fileContent;
900
+ if (append) {
901
+ let existing = null;
902
+ try {
903
+ existing = await fs.readFile(contextPath, 'utf-8');
904
+ }
905
+ catch {
906
+ // File doesn't exist yet
907
+ }
908
+ fileContent = (0, context_file_1.appendToContextFile)(existing, workspace, content, timestamp);
909
+ }
910
+ else {
911
+ fileContent = (0, context_file_1.formatContextFile)(workspace, content, timestamp);
912
+ }
913
+ await fs.writeFile(contextPath, fileContent, 'utf-8');
914
+ return {
915
+ saved: true,
916
+ path: contextPath,
917
+ workspace,
918
+ append: append ?? false,
919
+ timestamp,
920
+ };
921
+ });
922
+ }