@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.
- package/CHANGELOG.md +86 -0
- package/LICENSE +21 -0
- package/README.md +275 -0
- package/assets/README.md +53 -0
- package/assets/banner.png +0 -0
- package/assets/banner.svg +66 -0
- package/assets/favicon-32.png +0 -0
- package/assets/favicon-64.png +0 -0
- package/assets/icon-256.png +0 -0
- package/assets/icon.svg +23 -0
- package/assets/logo-256.png +0 -0
- package/assets/logo-512.png +0 -0
- package/assets/logo-wordmark.svg +35 -0
- package/assets/logo.svg +32 -0
- package/bin/workspace.js +130 -0
- package/dist/cli/ai-prompt.js +421 -0
- package/dist/cli/dashboard/AgentList.js +61 -0
- package/dist/cli/dashboard/DashboardSidebar.js +280 -0
- package/dist/cli/dashboard/HelpFooter.js +73 -0
- package/dist/cli/dashboard/StatusBadge.js +50 -0
- package/dist/cli/dashboard/index.js +11 -0
- package/dist/cli/dashboard/types.js +2 -0
- package/dist/cli/tui.js +48 -0
- package/dist/commands/ai-prompt.js +22 -0
- package/dist/commands/analyze-deps.js +138 -0
- package/dist/commands/archive.js +113 -0
- package/dist/commands/branch/create.js +119 -0
- package/dist/commands/branch/index.js +104 -0
- package/dist/commands/branch/merge.js +90 -0
- package/dist/commands/branch/rebase.js +84 -0
- package/dist/commands/branch/switch.js +165 -0
- package/dist/commands/cache/index.js +80 -0
- package/dist/commands/cache/manager.js +198 -0
- package/dist/commands/cleanup.js +153 -0
- package/dist/commands/configure-claude.js +55 -0
- package/dist/commands/configure.js +231 -0
- package/dist/commands/create.js +204 -0
- package/dist/commands/dashboard/hook-handler.js +162 -0
- package/dist/commands/dashboard/index.js +258 -0
- package/dist/commands/dashboard/launcher.js +40 -0
- package/dist/commands/dashboard/session-picker.js +106 -0
- package/dist/commands/dashboard/sidebar.js +39 -0
- package/dist/commands/dashboard/workspace-picker.js +95 -0
- package/dist/commands/delete.js +194 -0
- package/dist/commands/deprecated-aliases.js +44 -0
- package/dist/commands/diff.js +129 -0
- package/dist/commands/doctor.js +141 -0
- package/dist/commands/generate-docs.js +77 -0
- package/dist/commands/ghq-status.js +72 -0
- package/dist/commands/go.js +154 -0
- package/dist/commands/history.js +124 -0
- package/dist/commands/list.js +191 -0
- package/dist/commands/mcp/index.js +69 -0
- package/dist/commands/migrate.js +296 -0
- package/dist/commands/remove-repo.js +217 -0
- package/dist/commands/report-bug.js +112 -0
- package/dist/commands/run.js +147 -0
- package/dist/commands/save-context.js +171 -0
- package/dist/commands/sessions.js +129 -0
- package/dist/commands/status.js +138 -0
- package/dist/commands/suite/create.js +212 -0
- package/dist/commands/suite/delete.js +64 -0
- package/dist/commands/suite/export.js +126 -0
- package/dist/commands/suite/import.js +128 -0
- package/dist/commands/suite/index.js +94 -0
- package/dist/commands/suite/list.js +49 -0
- package/dist/commands/suite/use.js +227 -0
- package/dist/commands/sync.js +117 -0
- package/dist/commands/update.js +203 -0
- package/dist/mcp/install.js +550 -0
- package/dist/mcp/server.js +398 -0
- package/dist/mcp/tools.js +922 -0
- package/dist/pi-extensions/permission-sync.ts.template +41 -0
- package/dist/pi-extensions/permissions-gate.ts.template +69 -0
- package/dist/pi-extensions/pr-fix-trigger.ts.template +124 -0
- package/dist/pi-extensions/review-reminder.ts.template +58 -0
- package/dist/pi-extensions/workspace-input-status.ts.template +919 -0
- package/dist/program.js +136 -0
- package/dist/scripts/workspace-table.py +394 -0
- package/dist/types/dashboard.js +11 -0
- package/dist/types/index.js +2 -0
- package/dist/utils/agent-config.js +263 -0
- package/dist/utils/agent-state.js +178 -0
- package/dist/utils/banner.js +24 -0
- package/dist/utils/branch-operations.js +232 -0
- package/dist/utils/bug-report.js +355 -0
- package/dist/utils/cache.js +168 -0
- package/dist/utils/claude-integration.js +617 -0
- package/dist/utils/claude-sessions.js +260 -0
- package/dist/utils/cleanup-operations.js +120 -0
- package/dist/utils/colors.js +26 -0
- package/dist/utils/command-helpers.js +44 -0
- package/dist/utils/config.js +150 -0
- package/dist/utils/context-file.js +33 -0
- package/dist/utils/dashboard-hooks.js +172 -0
- package/dist/utils/dependency-analyzer.js +234 -0
- package/dist/utils/diff-operations.js +80 -0
- package/dist/utils/doc-generator.js +184 -0
- package/dist/utils/ghq-integration.js +332 -0
- package/dist/utils/git-operations.js +237 -0
- package/dist/utils/git-status.js +187 -0
- package/dist/utils/github.js +68 -0
- package/dist/utils/health-checks.js +345 -0
- package/dist/utils/history.js +150 -0
- package/dist/utils/hooks.js +100 -0
- package/dist/utils/logger.js +40 -0
- package/dist/utils/permission-sync.js +681 -0
- package/dist/utils/pi-extensions.js +224 -0
- package/dist/utils/progress.js +92 -0
- package/dist/utils/prompts.js +279 -0
- package/dist/utils/repo-resolver.js +260 -0
- package/dist/utils/retry.js +65 -0
- package/dist/utils/run-operations.js +30 -0
- package/dist/utils/suite.js +159 -0
- package/dist/utils/sync-operations.js +107 -0
- package/dist/utils/tmux-dashboard.js +184 -0
- package/dist/utils/validation.js +153 -0
- package/dist/utils/version-check.js +121 -0
- package/dist/utils/workspace-meta.js +185 -0
- package/install-shell-integration.sh +532 -0
- package/install.sh +401 -0
- package/package.json +90 -0
- package/remove-shell-block.awk +44 -0
- package/scripts/postinstall.js +85 -0
- package/skills/analyze-deps.md +23 -0
- package/skills/archive-workspace.md +26 -0
- package/skills/branch-create.md +18 -0
- package/skills/create-workspace.md +39 -0
- package/skills/delete-workspace.md +37 -0
- package/skills/list-org-repos.md +26 -0
- package/skills/list-suites.md +24 -0
- package/skills/list-workspaces.md +27 -0
- package/skills/refresh-workspace-docs.md +89 -0
- package/skills/remove-repo.md +25 -0
- package/skills/run-command.md +23 -0
- package/skills/save-context.md +68 -0
- package/skills/search-repos.md +31 -0
- package/skills/snapshot-list.md +18 -0
- package/skills/snapshot-save.md +12 -0
- package/skills/switch-branch.md +18 -0
- package/skills/update-cache.md +21 -0
- package/skills/update-workspace.md +66 -0
- package/skills/workspace-cleanup.md +18 -0
- package/skills/workspace-diff.md +23 -0
- package/skills/workspace-doctor.md +23 -0
- package/skills/workspace-info.md +45 -0
- package/skills/workspace-manager/SKILL.md +201 -0
- package/skills/workspace-manager/references/ai-prompt.md +44 -0
- package/skills/workspace-manager/references/analyze-deps.md +45 -0
- package/skills/workspace-manager/references/archive-workspace.md +38 -0
- package/skills/workspace-manager/references/branch-create.md +31 -0
- package/skills/workspace-manager/references/branch-merge.md +22 -0
- package/skills/workspace-manager/references/branch-rebase.md +22 -0
- package/skills/workspace-manager/references/cache-clear.md +18 -0
- package/skills/workspace-manager/references/cache-info.md +22 -0
- package/skills/workspace-manager/references/configure-claude.md +31 -0
- package/skills/workspace-manager/references/configure.md +35 -0
- package/skills/workspace-manager/references/create-workspace.md +45 -0
- package/skills/workspace-manager/references/dashboard.md +61 -0
- package/skills/workspace-manager/references/delete-workspace.md +33 -0
- package/skills/workspace-manager/references/generate-docs.md +36 -0
- package/skills/workspace-manager/references/ghq-status.md +28 -0
- package/skills/workspace-manager/references/go.md +33 -0
- package/skills/workspace-manager/references/history.md +41 -0
- package/skills/workspace-manager/references/list-org-repos.md +26 -0
- package/skills/workspace-manager/references/list-suites.md +21 -0
- package/skills/workspace-manager/references/list-workspaces.md +27 -0
- package/skills/workspace-manager/references/mcp.md +49 -0
- package/skills/workspace-manager/references/remove-repo.md +26 -0
- package/skills/workspace-manager/references/run-command.md +44 -0
- package/skills/workspace-manager/references/search-repos.md +24 -0
- package/skills/workspace-manager/references/sessions.md +27 -0
- package/skills/workspace-manager/references/suite-create.md +22 -0
- package/skills/workspace-manager/references/suite-delete.md +21 -0
- package/skills/workspace-manager/references/suite-export.md +19 -0
- package/skills/workspace-manager/references/suite-import.md +19 -0
- package/skills/workspace-manager/references/suite-use.md +27 -0
- package/skills/workspace-manager/references/switch-branch.md +25 -0
- package/skills/workspace-manager/references/update-cache.md +22 -0
- package/skills/workspace-manager/references/update-workspace.md +50 -0
- package/skills/workspace-manager/references/workspace-cleanup.md +33 -0
- package/skills/workspace-manager/references/workspace-diff.md +27 -0
- package/skills/workspace-manager/references/workspace-doctor.md +27 -0
- package/skills/workspace-manager/references/workspace-info.md +33 -0
- package/skills/workspace-manager/references/workspace-status.md +43 -0
- package/skills/workspace-manager/references/workspace-sync.md +26 -0
- package/skills/workspace-status.md +26 -0
- package/skills/workspace-sync.md +23 -0
- package/src/cli/ai-prompt.test.ts +667 -0
- package/src/cli/ai-prompt.ts +421 -0
- package/src/cli/dashboard/AgentList.tsx +44 -0
- package/src/cli/dashboard/DashboardSidebar.tsx +291 -0
- package/src/cli/dashboard/HelpFooter.tsx +22 -0
- package/src/cli/dashboard/StatusBadge.tsx +23 -0
- package/src/cli/dashboard/index.ts +5 -0
- package/src/cli/dashboard/types.ts +16 -0
- package/src/cli/tui.tsx +51 -0
- package/src/cli/workspace-cli.test.ts +30 -0
- package/src/commands/ai-prompt.ts +21 -0
- package/src/commands/analyze-deps.ts +119 -0
- package/src/commands/archive.ts +114 -0
- package/src/commands/branch/create.ts +94 -0
- package/src/commands/branch/index.ts +74 -0
- package/src/commands/branch/merge.ts +61 -0
- package/src/commands/branch/rebase.ts +54 -0
- package/src/commands/branch/switch.ts +150 -0
- package/src/commands/cache/index.ts +51 -0
- package/src/commands/cache/manager.ts +168 -0
- package/src/commands/cleanup.ts +132 -0
- package/src/commands/configure-claude.ts +55 -0
- package/src/commands/configure.ts +204 -0
- package/src/commands/create.ts +196 -0
- package/src/commands/dashboard/hook-handler.ts +138 -0
- package/src/commands/dashboard/index.ts +248 -0
- package/src/commands/dashboard/launcher.ts +42 -0
- package/src/commands/dashboard/session-picker.ts +73 -0
- package/src/commands/dashboard/sidebar.js +39 -0
- package/src/commands/dashboard/workspace-picker.ts +62 -0
- package/src/commands/delete.test.ts +250 -0
- package/src/commands/delete.ts +171 -0
- package/src/commands/deprecated-aliases.ts +55 -0
- package/src/commands/diff.ts +106 -0
- package/src/commands/doctor.ts +110 -0
- package/src/commands/generate-docs.ts +45 -0
- package/src/commands/ghq-status.ts +70 -0
- package/src/commands/go.ts +125 -0
- package/src/commands/history.ts +136 -0
- package/src/commands/list.test.ts +219 -0
- package/src/commands/list.ts +176 -0
- package/src/commands/mcp/index.ts +39 -0
- package/src/commands/migrate.ts +277 -0
- package/src/commands/remove-repo.ts +194 -0
- package/src/commands/report-bug.ts +88 -0
- package/src/commands/run.ts +125 -0
- package/src/commands/save-context.test.ts +113 -0
- package/src/commands/save-context.ts +141 -0
- package/src/commands/sessions.ts +99 -0
- package/src/commands/status.ts +111 -0
- package/src/commands/suite/create.ts +240 -0
- package/src/commands/suite/delete.ts +65 -0
- package/src/commands/suite/export.ts +97 -0
- package/src/commands/suite/import.ts +100 -0
- package/src/commands/suite/index.ts +66 -0
- package/src/commands/suite/list.ts +54 -0
- package/src/commands/suite/use.ts +232 -0
- package/src/commands/sync.ts +97 -0
- package/src/commands/update.ts +197 -0
- package/src/mcp/install.ts +529 -0
- package/src/mcp/server.ts +579 -0
- package/src/mcp/tools.test.ts +545 -0
- package/src/mcp/tools.ts +1019 -0
- package/src/mcp/update-workspace.test.ts +220 -0
- package/src/pi-extensions/permission-sync.ts.template +41 -0
- package/src/pi-extensions/permissions-gate.ts.template +69 -0
- package/src/pi-extensions/pr-fix-trigger.ts.template +124 -0
- package/src/pi-extensions/review-reminder.ts.template +58 -0
- package/src/pi-extensions/workspace-input-status.ts.template +919 -0
- package/src/program.ts +112 -0
- package/src/scripts/workspace-table.py +394 -0
- package/src/types/dashboard.ts +29 -0
- package/src/types/index.ts +109 -0
- package/src/types/ink.d.ts +171 -0
- package/src/utils/agent-config.test.ts +303 -0
- package/src/utils/agent-config.ts +264 -0
- package/src/utils/agent-state.test.ts +166 -0
- package/src/utils/agent-state.ts +135 -0
- package/src/utils/banner.ts +24 -0
- package/src/utils/branch-operations.ts +237 -0
- package/src/utils/bug-report.test.ts +174 -0
- package/src/utils/bug-report.ts +355 -0
- package/src/utils/cache.ts +150 -0
- package/src/utils/claude-integration.test.ts +381 -0
- package/src/utils/claude-integration.ts +649 -0
- package/src/utils/claude-sessions.test.ts +262 -0
- package/src/utils/claude-sessions.ts +251 -0
- package/src/utils/cleanup-operations.ts +94 -0
- package/src/utils/colors.ts +25 -0
- package/src/utils/command-helpers.ts +46 -0
- package/src/utils/config.test.ts +203 -0
- package/src/utils/config.ts +133 -0
- package/src/utils/context-file.ts +36 -0
- package/src/utils/dashboard-hooks.test.ts +168 -0
- package/src/utils/dashboard-hooks.ts +163 -0
- package/src/utils/dependency-analyzer.ts +243 -0
- package/src/utils/diff-operations.ts +59 -0
- package/src/utils/doc-generator.ts +187 -0
- package/src/utils/ghq-integration.test.ts +195 -0
- package/src/utils/ghq-integration.ts +317 -0
- package/src/utils/git-operations.test.ts +265 -0
- package/src/utils/git-operations.ts +233 -0
- package/src/utils/git-status.ts +171 -0
- package/src/utils/github.ts +74 -0
- package/src/utils/health-checks.ts +359 -0
- package/src/utils/history.ts +140 -0
- package/src/utils/hooks.ts +92 -0
- package/src/utils/logger.ts +37 -0
- package/src/utils/permission-sync.test.ts +593 -0
- package/src/utils/permission-sync.ts +720 -0
- package/src/utils/pi-extensions.test.ts +182 -0
- package/src/utils/pi-extensions.ts +226 -0
- package/src/utils/progress.ts +73 -0
- package/src/utils/prompts.test.ts +169 -0
- package/src/utils/prompts.ts +296 -0
- package/src/utils/repo-resolver.test.ts +313 -0
- package/src/utils/repo-resolver.ts +294 -0
- package/src/utils/retry.test.ts +124 -0
- package/src/utils/retry.ts +85 -0
- package/src/utils/run-operations.ts +38 -0
- package/src/utils/suite.test.ts +256 -0
- package/src/utils/suite.ts +144 -0
- package/src/utils/sync-operations.ts +87 -0
- package/src/utils/tmux-dashboard.ts +193 -0
- package/src/utils/validation.test.ts +139 -0
- package/src/utils/validation.ts +128 -0
- package/src/utils/version-check.test.ts +108 -0
- package/src/utils/version-check.ts +96 -0
- package/src/utils/workspace-meta.ts +173 -0
- package/sync-permissions.sh +137 -0
- package/tsconfig.json +20 -0
- 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
|
+
}
|