mstro-app 0.4.3 → 0.4.4
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/dist/server/cli/headless/claude-invoker-process.d.ts +11 -0
- package/dist/server/cli/headless/claude-invoker-process.d.ts.map +1 -0
- package/dist/server/cli/headless/claude-invoker-process.js +140 -0
- package/dist/server/cli/headless/claude-invoker-process.js.map +1 -0
- package/dist/server/cli/headless/claude-invoker-stall.d.ts +40 -0
- package/dist/server/cli/headless/claude-invoker-stall.d.ts.map +1 -0
- package/dist/server/cli/headless/claude-invoker-stall.js +98 -0
- package/dist/server/cli/headless/claude-invoker-stall.js.map +1 -0
- package/dist/server/cli/headless/claude-invoker-stream.d.ts +44 -0
- package/dist/server/cli/headless/claude-invoker-stream.d.ts.map +1 -0
- package/dist/server/cli/headless/claude-invoker-stream.js +276 -0
- package/dist/server/cli/headless/claude-invoker-stream.js.map +1 -0
- package/dist/server/cli/headless/claude-invoker-tools.d.ts +21 -0
- package/dist/server/cli/headless/claude-invoker-tools.d.ts.map +1 -0
- package/dist/server/cli/headless/claude-invoker-tools.js +137 -0
- package/dist/server/cli/headless/claude-invoker-tools.js.map +1 -0
- package/dist/server/cli/headless/claude-invoker.d.ts +6 -4
- package/dist/server/cli/headless/claude-invoker.d.ts.map +1 -1
- package/dist/server/cli/headless/claude-invoker.js +10 -807
- package/dist/server/cli/headless/claude-invoker.js.map +1 -1
- package/dist/server/cli/headless/haiku-assessments.d.ts +62 -0
- package/dist/server/cli/headless/haiku-assessments.d.ts.map +1 -0
- package/dist/server/cli/headless/haiku-assessments.js +281 -0
- package/dist/server/cli/headless/haiku-assessments.js.map +1 -0
- package/dist/server/cli/headless/headless-logger.d.ts +3 -2
- package/dist/server/cli/headless/headless-logger.d.ts.map +1 -1
- package/dist/server/cli/headless/headless-logger.js +28 -5
- package/dist/server/cli/headless/headless-logger.js.map +1 -1
- package/dist/server/cli/headless/native-timeout-detector.d.ts +44 -0
- package/dist/server/cli/headless/native-timeout-detector.d.ts.map +1 -0
- package/dist/server/cli/headless/native-timeout-detector.js +99 -0
- package/dist/server/cli/headless/native-timeout-detector.js.map +1 -0
- package/dist/server/cli/headless/stall-assessor.d.ts +2 -110
- package/dist/server/cli/headless/stall-assessor.d.ts.map +1 -1
- package/dist/server/cli/headless/stall-assessor.js +65 -457
- package/dist/server/cli/headless/stall-assessor.js.map +1 -1
- package/dist/server/cli/improvisation-attachments.d.ts +21 -0
- package/dist/server/cli/improvisation-attachments.d.ts.map +1 -0
- package/dist/server/cli/improvisation-attachments.js +116 -0
- package/dist/server/cli/improvisation-attachments.js.map +1 -0
- package/dist/server/cli/improvisation-retry.d.ts +52 -0
- package/dist/server/cli/improvisation-retry.d.ts.map +1 -0
- package/dist/server/cli/improvisation-retry.js +434 -0
- package/dist/server/cli/improvisation-retry.js.map +1 -0
- package/dist/server/cli/improvisation-session-manager.d.ts +10 -266
- package/dist/server/cli/improvisation-session-manager.d.ts.map +1 -1
- package/dist/server/cli/improvisation-session-manager.js +117 -1079
- package/dist/server/cli/improvisation-session-manager.js.map +1 -1
- package/dist/server/cli/improvisation-types.d.ts +86 -0
- package/dist/server/cli/improvisation-types.d.ts.map +1 -0
- package/dist/server/cli/improvisation-types.js +10 -0
- package/dist/server/cli/improvisation-types.js.map +1 -0
- package/dist/server/cli/prompt-builders.d.ts +68 -0
- package/dist/server/cli/prompt-builders.d.ts.map +1 -0
- package/dist/server/cli/prompt-builders.js +312 -0
- package/dist/server/cli/prompt-builders.js.map +1 -0
- package/dist/server/index.js +33 -212
- package/dist/server/index.js.map +1 -1
- package/dist/server/mcp/bouncer-haiku.d.ts +10 -0
- package/dist/server/mcp/bouncer-haiku.d.ts.map +1 -0
- package/dist/server/mcp/bouncer-haiku.js +152 -0
- package/dist/server/mcp/bouncer-haiku.js.map +1 -0
- package/dist/server/mcp/bouncer-integration.d.ts +3 -4
- package/dist/server/mcp/bouncer-integration.d.ts.map +1 -1
- package/dist/server/mcp/bouncer-integration.js +50 -196
- package/dist/server/mcp/bouncer-integration.js.map +1 -1
- package/dist/server/mcp/security-analysis.d.ts +38 -0
- package/dist/server/mcp/security-analysis.d.ts.map +1 -0
- package/dist/server/mcp/security-analysis.js +183 -0
- package/dist/server/mcp/security-analysis.js.map +1 -0
- package/dist/server/mcp/security-audit.d.ts +1 -1
- package/dist/server/mcp/security-audit.d.ts.map +1 -1
- package/dist/server/mcp/security-patterns.d.ts +1 -25
- package/dist/server/mcp/security-patterns.d.ts.map +1 -1
- package/dist/server/mcp/security-patterns.js +55 -260
- package/dist/server/mcp/security-patterns.js.map +1 -1
- package/dist/server/server-setup.d.ts +22 -0
- package/dist/server/server-setup.d.ts.map +1 -0
- package/dist/server/server-setup.js +101 -0
- package/dist/server/server-setup.js.map +1 -0
- package/dist/server/services/file-explorer-ops.d.ts +24 -0
- package/dist/server/services/file-explorer-ops.d.ts.map +1 -0
- package/dist/server/services/file-explorer-ops.js +211 -0
- package/dist/server/services/file-explorer-ops.js.map +1 -0
- package/dist/server/services/files.d.ts +2 -85
- package/dist/server/services/files.d.ts.map +1 -1
- package/dist/server/services/files.js +7 -427
- package/dist/server/services/files.js.map +1 -1
- package/dist/server/services/plan/composer.d.ts.map +1 -1
- package/dist/server/services/plan/composer.js +2 -1
- package/dist/server/services/plan/composer.js.map +1 -1
- package/dist/server/services/plan/executor.d.ts.map +1 -1
- package/dist/server/services/plan/executor.js +3 -1
- package/dist/server/services/plan/executor.js.map +1 -1
- package/dist/server/services/plan/parser-core.d.ts +20 -0
- package/dist/server/services/plan/parser-core.d.ts.map +1 -0
- package/dist/server/services/plan/parser-core.js +350 -0
- package/dist/server/services/plan/parser-core.js.map +1 -0
- package/dist/server/services/plan/parser-migration.d.ts +5 -0
- package/dist/server/services/plan/parser-migration.d.ts.map +1 -0
- package/dist/server/services/plan/parser-migration.js +124 -0
- package/dist/server/services/plan/parser-migration.js.map +1 -0
- package/dist/server/services/plan/parser.d.ts +0 -8
- package/dist/server/services/plan/parser.d.ts.map +1 -1
- package/dist/server/services/plan/parser.js +50 -569
- package/dist/server/services/plan/parser.js.map +1 -1
- package/dist/server/services/plan/review-gate.d.ts +2 -0
- package/dist/server/services/plan/review-gate.d.ts.map +1 -1
- package/dist/server/services/plan/review-gate.js +2 -2
- package/dist/server/services/plan/review-gate.js.map +1 -1
- package/dist/server/services/plan/types.d.ts +2 -0
- package/dist/server/services/plan/types.d.ts.map +1 -1
- package/dist/server/services/platform-credentials.d.ts +24 -0
- package/dist/server/services/platform-credentials.d.ts.map +1 -0
- package/dist/server/services/platform-credentials.js +68 -0
- package/dist/server/services/platform-credentials.js.map +1 -0
- package/dist/server/services/platform.d.ts +1 -31
- package/dist/server/services/platform.d.ts.map +1 -1
- package/dist/server/services/platform.js +10 -119
- package/dist/server/services/platform.js.map +1 -1
- package/dist/server/services/terminal/pty-manager.d.ts +7 -97
- package/dist/server/services/terminal/pty-manager.d.ts.map +1 -1
- package/dist/server/services/terminal/pty-manager.js +53 -266
- package/dist/server/services/terminal/pty-manager.js.map +1 -1
- package/dist/server/services/terminal/pty-utils.d.ts +57 -0
- package/dist/server/services/terminal/pty-utils.d.ts.map +1 -0
- package/dist/server/services/terminal/pty-utils.js +141 -0
- package/dist/server/services/terminal/pty-utils.js.map +1 -0
- package/dist/server/services/websocket/file-definition-handlers.d.ts +4 -0
- package/dist/server/services/websocket/file-definition-handlers.d.ts.map +1 -0
- package/dist/server/services/websocket/file-definition-handlers.js +153 -0
- package/dist/server/services/websocket/file-definition-handlers.js.map +1 -0
- package/dist/server/services/websocket/file-explorer-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/file-explorer-handlers.js +52 -391
- package/dist/server/services/websocket/file-explorer-handlers.js.map +1 -1
- package/dist/server/services/websocket/file-search-handlers.d.ts +5 -0
- package/dist/server/services/websocket/file-search-handlers.d.ts.map +1 -0
- package/dist/server/services/websocket/file-search-handlers.js +238 -0
- package/dist/server/services/websocket/file-search-handlers.js.map +1 -0
- package/dist/server/services/websocket/file-utils.js +3 -3
- package/dist/server/services/websocket/file-utils.js.map +1 -1
- package/dist/server/services/websocket/git-branch-handlers.d.ts +7 -0
- package/dist/server/services/websocket/git-branch-handlers.d.ts.map +1 -0
- package/dist/server/services/websocket/git-branch-handlers.js +110 -0
- package/dist/server/services/websocket/git-branch-handlers.js.map +1 -0
- package/dist/server/services/websocket/git-diff-handlers.d.ts +6 -0
- package/dist/server/services/websocket/git-diff-handlers.d.ts.map +1 -0
- package/dist/server/services/websocket/git-diff-handlers.js +123 -0
- package/dist/server/services/websocket/git-diff-handlers.js.map +1 -0
- package/dist/server/services/websocket/git-handlers.d.ts +2 -31
- package/dist/server/services/websocket/git-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/git-handlers.js +35 -541
- package/dist/server/services/websocket/git-handlers.js.map +1 -1
- package/dist/server/services/websocket/git-log-handlers.d.ts +6 -0
- package/dist/server/services/websocket/git-log-handlers.d.ts.map +1 -0
- package/dist/server/services/websocket/git-log-handlers.js +128 -0
- package/dist/server/services/websocket/git-log-handlers.js.map +1 -0
- package/dist/server/services/websocket/git-pr-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/git-pr-handlers.js +13 -53
- package/dist/server/services/websocket/git-pr-handlers.js.map +1 -1
- package/dist/server/services/websocket/git-tag-handlers.d.ts +6 -0
- package/dist/server/services/websocket/git-tag-handlers.d.ts.map +1 -0
- package/dist/server/services/websocket/git-tag-handlers.js +76 -0
- package/dist/server/services/websocket/git-tag-handlers.js.map +1 -0
- package/dist/server/services/websocket/git-utils.d.ts +43 -0
- package/dist/server/services/websocket/git-utils.d.ts.map +1 -0
- package/dist/server/services/websocket/git-utils.js +201 -0
- package/dist/server/services/websocket/git-utils.js.map +1 -0
- package/dist/server/services/websocket/handler.d.ts +2 -0
- package/dist/server/services/websocket/handler.d.ts.map +1 -1
- package/dist/server/services/websocket/handler.js +37 -126
- package/dist/server/services/websocket/handler.js.map +1 -1
- package/dist/server/services/websocket/plan-board-handlers.d.ts +11 -0
- package/dist/server/services/websocket/plan-board-handlers.d.ts.map +1 -0
- package/dist/server/services/websocket/plan-board-handlers.js +218 -0
- package/dist/server/services/websocket/plan-board-handlers.js.map +1 -0
- package/dist/server/services/websocket/plan-execution-handlers.d.ts +9 -0
- package/dist/server/services/websocket/plan-execution-handlers.d.ts.map +1 -0
- package/dist/server/services/websocket/plan-execution-handlers.js +142 -0
- package/dist/server/services/websocket/plan-execution-handlers.js.map +1 -0
- package/dist/server/services/websocket/plan-handlers.d.ts +7 -2
- package/dist/server/services/websocket/plan-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/plan-handlers.js +6 -925
- package/dist/server/services/websocket/plan-handlers.js.map +1 -1
- package/dist/server/services/websocket/plan-helpers.d.ts +19 -0
- package/dist/server/services/websocket/plan-helpers.d.ts.map +1 -0
- package/dist/server/services/websocket/plan-helpers.js +199 -0
- package/dist/server/services/websocket/plan-helpers.js.map +1 -0
- package/dist/server/services/websocket/plan-issue-handlers.d.ts +12 -0
- package/dist/server/services/websocket/plan-issue-handlers.d.ts.map +1 -0
- package/dist/server/services/websocket/plan-issue-handlers.js +162 -0
- package/dist/server/services/websocket/plan-issue-handlers.js.map +1 -0
- package/dist/server/services/websocket/plan-sprint-handlers.d.ts +7 -0
- package/dist/server/services/websocket/plan-sprint-handlers.d.ts.map +1 -0
- package/dist/server/services/websocket/plan-sprint-handlers.js +206 -0
- package/dist/server/services/websocket/plan-sprint-handlers.js.map +1 -0
- package/dist/server/services/websocket/quality-complexity.d.ts +14 -0
- package/dist/server/services/websocket/quality-complexity.d.ts.map +1 -0
- package/dist/server/services/websocket/quality-complexity.js +262 -0
- package/dist/server/services/websocket/quality-complexity.js.map +1 -0
- package/dist/server/services/websocket/quality-fix-agent.d.ts +16 -0
- package/dist/server/services/websocket/quality-fix-agent.d.ts.map +1 -0
- package/dist/server/services/websocket/quality-fix-agent.js +140 -0
- package/dist/server/services/websocket/quality-fix-agent.js.map +1 -0
- package/dist/server/services/websocket/quality-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/quality-handlers.js +34 -346
- package/dist/server/services/websocket/quality-handlers.js.map +1 -1
- package/dist/server/services/websocket/quality-linting.d.ts +9 -0
- package/dist/server/services/websocket/quality-linting.d.ts.map +1 -0
- package/dist/server/services/websocket/quality-linting.js +178 -0
- package/dist/server/services/websocket/quality-linting.js.map +1 -0
- package/dist/server/services/websocket/quality-review-agent.d.ts +19 -0
- package/dist/server/services/websocket/quality-review-agent.d.ts.map +1 -0
- package/dist/server/services/websocket/quality-review-agent.js +206 -0
- package/dist/server/services/websocket/quality-review-agent.js.map +1 -0
- package/dist/server/services/websocket/quality-service.d.ts +3 -51
- package/dist/server/services/websocket/quality-service.d.ts.map +1 -1
- package/dist/server/services/websocket/quality-service.js +9 -651
- package/dist/server/services/websocket/quality-service.js.map +1 -1
- package/dist/server/services/websocket/quality-tools.d.ts +23 -0
- package/dist/server/services/websocket/quality-tools.d.ts.map +1 -0
- package/dist/server/services/websocket/quality-tools.js +208 -0
- package/dist/server/services/websocket/quality-tools.js.map +1 -0
- package/dist/server/services/websocket/quality-types.d.ts +59 -0
- package/dist/server/services/websocket/quality-types.d.ts.map +1 -0
- package/dist/server/services/websocket/quality-types.js +101 -0
- package/dist/server/services/websocket/quality-types.js.map +1 -0
- package/dist/server/services/websocket/session-handlers.d.ts +3 -4
- package/dist/server/services/websocket/session-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/session-handlers.js +3 -378
- package/dist/server/services/websocket/session-handlers.js.map +1 -1
- package/dist/server/services/websocket/session-history.d.ts +4 -0
- package/dist/server/services/websocket/session-history.d.ts.map +1 -0
- package/dist/server/services/websocket/session-history.js +208 -0
- package/dist/server/services/websocket/session-history.js.map +1 -0
- package/dist/server/services/websocket/session-initialization.d.ts +5 -0
- package/dist/server/services/websocket/session-initialization.d.ts.map +1 -0
- package/dist/server/services/websocket/session-initialization.js +163 -0
- package/dist/server/services/websocket/session-initialization.js.map +1 -0
- package/dist/server/services/websocket/types.d.ts +12 -2
- package/dist/server/services/websocket/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/server/cli/headless/claude-invoker-process.ts +204 -0
- package/server/cli/headless/claude-invoker-stall.ts +164 -0
- package/server/cli/headless/claude-invoker-stream.ts +353 -0
- package/server/cli/headless/claude-invoker-tools.ts +187 -0
- package/server/cli/headless/claude-invoker.ts +15 -1096
- package/server/cli/headless/haiku-assessments.ts +365 -0
- package/server/cli/headless/headless-logger.ts +26 -5
- package/server/cli/headless/native-timeout-detector.ts +117 -0
- package/server/cli/headless/stall-assessor.ts +65 -618
- package/server/cli/improvisation-attachments.ts +148 -0
- package/server/cli/improvisation-retry.ts +602 -0
- package/server/cli/improvisation-session-manager.ts +140 -1349
- package/server/cli/improvisation-types.ts +98 -0
- package/server/cli/prompt-builders.ts +370 -0
- package/server/index.ts +35 -246
- package/server/mcp/bouncer-haiku.ts +182 -0
- package/server/mcp/bouncer-integration.ts +87 -248
- package/server/mcp/security-analysis.ts +217 -0
- package/server/mcp/security-audit.ts +1 -1
- package/server/mcp/security-patterns.ts +60 -283
- package/server/server-setup.ts +114 -0
- package/server/services/file-explorer-ops.ts +293 -0
- package/server/services/files.ts +20 -532
- package/server/services/plan/composer.ts +2 -1
- package/server/services/plan/executor.ts +3 -1
- package/server/services/plan/parser-core.ts +406 -0
- package/server/services/plan/parser-migration.ts +128 -0
- package/server/services/plan/parser.ts +52 -620
- package/server/services/plan/review-gate.ts +4 -2
- package/server/services/plan/types.ts +2 -0
- package/server/services/platform-credentials.ts +83 -0
- package/server/services/platform.ts +15 -141
- package/server/services/terminal/pty-manager.ts +66 -313
- package/server/services/terminal/pty-utils.ts +176 -0
- package/server/services/websocket/file-definition-handlers.ts +165 -0
- package/server/services/websocket/file-explorer-handlers.ts +37 -452
- package/server/services/websocket/file-search-handlers.ts +291 -0
- package/server/services/websocket/file-utils.ts +3 -3
- package/server/services/websocket/git-branch-handlers.ts +130 -0
- package/server/services/websocket/git-diff-handlers.ts +140 -0
- package/server/services/websocket/git-handlers.ts +40 -625
- package/server/services/websocket/git-log-handlers.ts +149 -0
- package/server/services/websocket/git-pr-handlers.ts +17 -62
- package/server/services/websocket/git-tag-handlers.ts +91 -0
- package/server/services/websocket/git-utils.ts +230 -0
- package/server/services/websocket/handler.ts +39 -126
- package/server/services/websocket/plan-board-handlers.ts +277 -0
- package/server/services/websocket/plan-execution-handlers.ts +184 -0
- package/server/services/websocket/plan-handlers.ts +8 -1114
- package/server/services/websocket/plan-helpers.ts +215 -0
- package/server/services/websocket/plan-issue-handlers.ts +204 -0
- package/server/services/websocket/plan-sprint-handlers.ts +252 -0
- package/server/services/websocket/quality-complexity.ts +294 -0
- package/server/services/websocket/quality-fix-agent.ts +181 -0
- package/server/services/websocket/quality-handlers.ts +36 -404
- package/server/services/websocket/quality-linting.ts +187 -0
- package/server/services/websocket/quality-review-agent.ts +246 -0
- package/server/services/websocket/quality-service.ts +11 -762
- package/server/services/websocket/quality-tools.ts +209 -0
- package/server/services/websocket/quality-types.ts +169 -0
- package/server/services/websocket/session-handlers.ts +5 -437
- package/server/services/websocket/session-history.ts +222 -0
- package/server/services/websocket/session-initialization.ts +209 -0
- package/server/services/websocket/types.ts +17 -0
|
@@ -1,185 +1,18 @@
|
|
|
1
1
|
// Copyright (c) 2025-present Mstro, Inc. All rights reserved.
|
|
2
2
|
// Licensed under the MIT License. See LICENSE file for details.
|
|
3
3
|
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
4
|
+
import { handleGitCheckout, handleGitCreateBranch, handleGitDeleteBranch, handleGitListBranches } from './git-branch-handlers.js';
|
|
5
|
+
import { handleGitCommitDiff, handleGitDiff, handleGitShowCommit } from './git-diff-handlers.js';
|
|
6
|
+
import { handleGitDiscoverRepos, handleGitLog, handleGitSetDirectory } from './git-log-handlers.js';
|
|
7
7
|
import { handleGitPRMessage } from './git-pr-handlers.js';
|
|
8
|
+
import { handleGitCreateTag, handleGitListTags, handleGitPushTag } from './git-tag-handlers.js';
|
|
9
|
+
import { executeGitCommand, parseGitStatus, sendGitError, spawnHaikuWithPrompt, stripCoauthorLines, truncateDiff } from './git-utils.js';
|
|
8
10
|
import { handleGitWorktreeMessage } from './git-worktree-handlers.js';
|
|
9
11
|
import type { HandlerContext } from './handler-context.js';
|
|
10
|
-
import type {
|
|
12
|
+
import type { GitStatusResponse, WebSocketMessage, WSContext } from './types.js';
|
|
11
13
|
|
|
12
|
-
|
|
13
|
-
export
|
|
14
|
-
if (remoteUrl.includes('github.com')) return 'github';
|
|
15
|
-
if (remoteUrl.includes('gitlab.com') || remoteUrl.includes('gitlab')) return 'gitlab';
|
|
16
|
-
return 'unknown';
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/** Execute a git command and return stdout */
|
|
20
|
-
export function executeGitCommand(args: string[], workingDir: string): Promise<{ stdout: string; stderr: string; exitCode: number }> {
|
|
21
|
-
return new Promise((resolve) => {
|
|
22
|
-
const git = spawn('git', args, {
|
|
23
|
-
cwd: workingDir,
|
|
24
|
-
stdio: ['ignore', 'pipe', 'pipe']
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
let stdout = '';
|
|
28
|
-
let stderr = '';
|
|
29
|
-
|
|
30
|
-
git.stdout?.on('data', (data: Buffer) => {
|
|
31
|
-
stdout += data.toString();
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
git.stderr?.on('data', (data: Buffer) => {
|
|
35
|
-
stderr += data.toString();
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
git.on('close', (code: number | null) => {
|
|
39
|
-
resolve({ stdout, stderr, exitCode: code ?? 1 });
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
git.on('error', (err: Error) => {
|
|
43
|
-
resolve({ stdout: '', stderr: err.message, exitCode: 1 });
|
|
44
|
-
});
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/** Map of simple escape sequences to their character values */
|
|
49
|
-
const ESCAPE_CHARS: Record<string, string> = {
|
|
50
|
-
'\\': '\\',
|
|
51
|
-
'"': '"',
|
|
52
|
-
'n': '\n',
|
|
53
|
-
't': '\t',
|
|
54
|
-
'r': '\r',
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
/** Check if position i starts an octal escape sequence (\nnn) */
|
|
58
|
-
function isOctalEscape(str: string, i: number): boolean {
|
|
59
|
-
return i + 3 < str.length &&
|
|
60
|
-
/[0-7]/.test(str[i + 1]) &&
|
|
61
|
-
/[0-7]{2}/.test(str.slice(i + 2, i + 4));
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Unquote a git-quoted path (C-style quoting)
|
|
66
|
-
*/
|
|
67
|
-
export function unquoteGitPath(path: string): string {
|
|
68
|
-
if (!path.startsWith('"') || !path.endsWith('"')) {
|
|
69
|
-
return path;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const inner = path.slice(1, -1);
|
|
73
|
-
let result = '';
|
|
74
|
-
let i = 0;
|
|
75
|
-
|
|
76
|
-
while (i < inner.length) {
|
|
77
|
-
if (inner[i] !== '\\' || i + 1 >= inner.length) {
|
|
78
|
-
result += inner[i];
|
|
79
|
-
i++;
|
|
80
|
-
continue;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
const next = inner[i + 1];
|
|
84
|
-
const escaped = ESCAPE_CHARS[next];
|
|
85
|
-
|
|
86
|
-
if (escaped !== undefined) {
|
|
87
|
-
result += escaped;
|
|
88
|
-
i += 2;
|
|
89
|
-
} else if (isOctalEscape(inner, i)) {
|
|
90
|
-
result += String.fromCharCode(parseInt(inner.slice(i + 1, i + 4), 8));
|
|
91
|
-
i += 4;
|
|
92
|
-
} else {
|
|
93
|
-
result += inner[i];
|
|
94
|
-
i++;
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
return result;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/** Parse git status --porcelain output into structured format */
|
|
102
|
-
export function parseGitStatus(porcelainOutput: string): { staged: GitFileStatus[]; unstaged: GitFileStatus[]; untracked: GitFileStatus[] } {
|
|
103
|
-
const staged: GitFileStatus[] = [];
|
|
104
|
-
const unstaged: GitFileStatus[] = [];
|
|
105
|
-
const untracked: GitFileStatus[] = [];
|
|
106
|
-
|
|
107
|
-
const lines = porcelainOutput.split('\n').filter(line => line.length >= 4);
|
|
108
|
-
|
|
109
|
-
for (const line of lines) {
|
|
110
|
-
const indexStatus = line[0];
|
|
111
|
-
const workTreeStatus = line[1];
|
|
112
|
-
const rawPath = line.slice(3);
|
|
113
|
-
|
|
114
|
-
const path = unquoteGitPath(rawPath);
|
|
115
|
-
|
|
116
|
-
let filePath = path;
|
|
117
|
-
let originalPath: string | undefined;
|
|
118
|
-
if (rawPath.includes(' -> ')) {
|
|
119
|
-
const parts = rawPath.split(' -> ');
|
|
120
|
-
originalPath = unquoteGitPath(parts[0]);
|
|
121
|
-
filePath = unquoteGitPath(parts[1]);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
if (indexStatus === '?' && workTreeStatus === '?') {
|
|
125
|
-
untracked.push({ path: filePath, status: '?', staged: false });
|
|
126
|
-
continue;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
if (indexStatus !== ' ' && indexStatus !== '?') {
|
|
130
|
-
staged.push({ path: filePath, status: indexStatus as GitFileStatus['status'], staged: true, originalPath });
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
if (workTreeStatus !== ' ' && workTreeStatus !== '?') {
|
|
134
|
-
unstaged.push({ path: filePath, status: workTreeStatus as GitFileStatus['status'], staged: false, originalPath });
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
return { staged, unstaged, untracked };
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/** Check if a binary runs successfully (exit code 0) */
|
|
142
|
-
export function spawnCheck(bin: string, args: string[]): Promise<boolean> {
|
|
143
|
-
return new Promise((resolve) => {
|
|
144
|
-
const proc = spawn(bin, args, { stdio: ['ignore', 'pipe', 'pipe'] });
|
|
145
|
-
proc.on('close', (code) => resolve(code === 0));
|
|
146
|
-
proc.on('error', () => resolve(false));
|
|
147
|
-
});
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/** Spawn a process and capture stdout/stderr */
|
|
151
|
-
export function spawnWithOutput(bin: string, args: string[], cwd: string): Promise<{ stdout: string; stderr: string; exitCode: number }> {
|
|
152
|
-
return new Promise((resolve) => {
|
|
153
|
-
const proc = spawn(bin, args, { cwd, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
154
|
-
let stdout = '';
|
|
155
|
-
let stderr = '';
|
|
156
|
-
proc.stdout?.on('data', (d: Buffer) => { stdout += d.toString(); });
|
|
157
|
-
proc.stderr?.on('data', (d: Buffer) => { stderr += d.toString(); });
|
|
158
|
-
proc.on('close', (code) => resolve({ stdout, stderr, exitCode: code ?? 1 }));
|
|
159
|
-
proc.on('error', (err: Error) => resolve({ stdout: '', stderr: err.message, exitCode: 1 }));
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* Strip injected coauthor/attribution lines from a commit message.
|
|
165
|
-
*/
|
|
166
|
-
export function stripCoauthorLines(message: string): string {
|
|
167
|
-
const lines = message.split('\n');
|
|
168
|
-
const markers = ['co-authored', 'authored-by', 'haiku', 'noreply@anthropic.com'];
|
|
169
|
-
const result: string[] = [];
|
|
170
|
-
for (let i = 0; i < lines.length; i++) {
|
|
171
|
-
const lower = lines[i].toLowerCase();
|
|
172
|
-
if (markers.some(m => lower.includes(m))) {
|
|
173
|
-
if (result.length > 0 && result[result.length - 1].trim() === '') {
|
|
174
|
-
result.pop();
|
|
175
|
-
}
|
|
176
|
-
continue;
|
|
177
|
-
}
|
|
178
|
-
result.push(lines[i]);
|
|
179
|
-
}
|
|
180
|
-
if (result.length === 0) return '';
|
|
181
|
-
return result.join('\n').trimEnd();
|
|
182
|
-
}
|
|
14
|
+
// Re-export utilities for backward compatibility (git-pr-handlers, git-worktree-handlers import from here)
|
|
15
|
+
export { detectGitProvider, executeGitCommand, parseGitStatus, sendGitError, spawnCheck, spawnHaikuWithPrompt, spawnWithOutput, stripCoauthorLines, truncateDiff, unquoteGitPath } from './git-utils.js';
|
|
183
16
|
|
|
184
17
|
// PR message types that route to git-pr-handlers
|
|
185
18
|
const GIT_PR_TYPES = new Set([
|
|
@@ -222,6 +55,8 @@ export async function handleGitMessage(ctx: HandlerContext, ws: WSContext, msg:
|
|
|
222
55
|
gitCreateBranch: () => handleGitCreateBranch(ctx, ws, msg, tabId, gitDir),
|
|
223
56
|
gitDeleteBranch: () => handleGitDeleteBranch(ctx, ws, msg, tabId, gitDir),
|
|
224
57
|
gitDiff: () => handleGitDiff(ctx, ws, msg, tabId, gitDir),
|
|
58
|
+
gitShowCommit: () => handleGitShowCommit(ctx, ws, msg, tabId, gitDir),
|
|
59
|
+
gitCommitDiff: () => handleGitCommitDiff(ctx, ws, msg, tabId, gitDir),
|
|
225
60
|
gitListTags: () => handleGitListTags(ctx, ws, tabId, gitDir),
|
|
226
61
|
gitCreateTag: () => handleGitCreateTag(ctx, ws, msg, tabId, gitDir),
|
|
227
62
|
gitPushTag: () => handleGitPushTag(ctx, ws, msg, tabId, gitDir),
|
|
@@ -271,7 +106,7 @@ export async function handleGitStatus(ctx: HandlerContext, ws: WSContext, tabId:
|
|
|
271
106
|
|
|
272
107
|
ctx.send(ws, { type: 'gitStatus', tabId, data: response });
|
|
273
108
|
} catch (error: unknown) {
|
|
274
|
-
ctx
|
|
109
|
+
sendGitError(ctx, ws, tabId, error);
|
|
275
110
|
}
|
|
276
111
|
}
|
|
277
112
|
|
|
@@ -294,7 +129,7 @@ async function handleGitStage(ctx: HandlerContext, ws: WSContext, msg: WebSocket
|
|
|
294
129
|
|
|
295
130
|
ctx.send(ws, { type: 'gitStaged', tabId, data: { paths: paths || [] } });
|
|
296
131
|
} catch (error: unknown) {
|
|
297
|
-
ctx
|
|
132
|
+
sendGitError(ctx, ws, tabId, error);
|
|
298
133
|
}
|
|
299
134
|
}
|
|
300
135
|
|
|
@@ -314,7 +149,7 @@ async function handleGitUnstage(ctx: HandlerContext, ws: WSContext, msg: WebSock
|
|
|
314
149
|
|
|
315
150
|
ctx.send(ws, { type: 'gitUnstaged', tabId, data: { paths } });
|
|
316
151
|
} catch (error: unknown) {
|
|
317
|
-
ctx
|
|
152
|
+
sendGitError(ctx, ws, tabId, error);
|
|
318
153
|
}
|
|
319
154
|
}
|
|
320
155
|
|
|
@@ -343,7 +178,7 @@ async function handleGitCommit(ctx: HandlerContext, ws: WSContext, msg: WebSocke
|
|
|
343
178
|
ctx.send(ws, { type: 'gitCommitted', tabId, data: { hash, message } });
|
|
344
179
|
handleGitStatus(ctx, ws, tabId, workingDir);
|
|
345
180
|
} catch (error: unknown) {
|
|
346
|
-
ctx
|
|
181
|
+
sendGitError(ctx, ws, tabId, error);
|
|
347
182
|
}
|
|
348
183
|
}
|
|
349
184
|
|
|
@@ -358,31 +193,18 @@ async function handleGitCommitWithAI(ctx: HandlerContext, ws: WSContext, msg: We
|
|
|
358
193
|
}
|
|
359
194
|
|
|
360
195
|
const diffResult = await executeGitCommand(['diff', '--cached'], workingDir);
|
|
361
|
-
const diff = diffResult.stdout;
|
|
362
|
-
|
|
363
196
|
const logResult = await executeGitCommand(['log', '--oneline', '-5'], workingDir);
|
|
364
|
-
const recentCommits = logResult.stdout.trim();
|
|
365
|
-
|
|
366
|
-
const tempDir = join(workingDir, '.mstro', 'tmp');
|
|
367
|
-
if (!existsSync(tempDir)) {
|
|
368
|
-
mkdirSync(tempDir, { recursive: true });
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
let truncatedDiff = diff;
|
|
372
|
-
if (diff.length > 8000) {
|
|
373
|
-
truncatedDiff = `${diff.slice(0, 4000)}\n\n... [diff truncated] ...\n\n${diff.slice(-3500)}`;
|
|
374
|
-
}
|
|
375
197
|
|
|
376
198
|
const prompt = `You are generating a git commit message for the following staged changes.
|
|
377
199
|
|
|
378
200
|
RECENT COMMIT MESSAGES (for style reference):
|
|
379
|
-
${
|
|
201
|
+
${logResult.stdout.trim() || 'No recent commits'}
|
|
380
202
|
|
|
381
203
|
STAGED FILES:
|
|
382
204
|
${staged.map(f => `${f.status} ${f.path}`).join('\n')}
|
|
383
205
|
|
|
384
206
|
DIFF OF STAGED CHANGES:
|
|
385
|
-
${
|
|
207
|
+
${truncateDiff(diffResult.stdout)}
|
|
386
208
|
|
|
387
209
|
Generate a commit message following these rules:
|
|
388
210
|
1. First line: imperative mood, max 72 characters (e.g., "Add user authentication", "Fix memory leak in parser")
|
|
@@ -393,78 +215,36 @@ Generate a commit message following these rules:
|
|
|
393
215
|
|
|
394
216
|
Respond with ONLY the commit message, nothing else.`;
|
|
395
217
|
|
|
396
|
-
const
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
const args = [
|
|
402
|
-
'--print',
|
|
403
|
-
'--model', 'haiku',
|
|
404
|
-
'--system-prompt', systemPrompt,
|
|
405
|
-
promptFile
|
|
406
|
-
];
|
|
407
|
-
|
|
408
|
-
const claude = spawn('claude', args, {
|
|
409
|
-
cwd: workingDir,
|
|
410
|
-
stdio: ['ignore', 'pipe', 'pipe']
|
|
411
|
-
});
|
|
412
|
-
|
|
413
|
-
let stdout = '';
|
|
414
|
-
let stderr = '';
|
|
218
|
+
const result = await spawnHaikuWithPrompt(
|
|
219
|
+
prompt,
|
|
220
|
+
'You are a commit message assistant. Respond with only the commit message, no preamble or explanation.',
|
|
221
|
+
workingDir,
|
|
222
|
+
);
|
|
415
223
|
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
224
|
+
if (result.exitCode !== 0 || !result.stdout.trim()) {
|
|
225
|
+
console.error('[WebSocketImproviseHandler] Claude commit message error:', result.stderr || 'No output');
|
|
226
|
+
ctx.send(ws, { type: 'gitError', tabId, data: { error: 'Failed to generate commit message' } });
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
419
229
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
});
|
|
230
|
+
const commitMessage = extractCommitMessage(result.stdout.trim());
|
|
231
|
+
const autoCommit = !!msg.data?.autoCommit;
|
|
423
232
|
|
|
424
|
-
|
|
425
|
-
try {
|
|
426
|
-
unlinkSync(promptFile);
|
|
427
|
-
} catch {
|
|
428
|
-
// Ignore cleanup errors
|
|
429
|
-
}
|
|
233
|
+
ctx.send(ws, { type: 'gitCommitMessage', tabId, data: { message: commitMessage, autoCommit } });
|
|
430
234
|
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
235
|
+
if (autoCommit) {
|
|
236
|
+
const commitResult = await executeGitCommand(['commit', '-m', commitMessage], workingDir);
|
|
237
|
+
if (commitResult.exitCode !== 0) {
|
|
238
|
+
ctx.send(ws, { type: 'gitError', tabId, data: { error: commitResult.stderr || commitResult.stdout || 'Failed to commit' } });
|
|
434
239
|
return;
|
|
435
240
|
}
|
|
436
241
|
|
|
437
|
-
const
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
if (msg.data?.autoCommit) {
|
|
443
|
-
const commitResult = await executeGitCommand(['commit', '-m', commitMessage], workingDir);
|
|
444
|
-
if (commitResult.exitCode !== 0) {
|
|
445
|
-
ctx.send(ws, { type: 'gitError', tabId, data: { error: commitResult.stderr || commitResult.stdout || 'Failed to commit' } });
|
|
446
|
-
return;
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
const hashResult = await executeGitCommand(['rev-parse', '--short', 'HEAD'], workingDir);
|
|
450
|
-
const hash = hashResult.stdout.trim();
|
|
451
|
-
|
|
452
|
-
ctx.send(ws, { type: 'gitCommitted', tabId, data: { hash, message: commitMessage } });
|
|
453
|
-
handleGitStatus(ctx, ws, tabId, workingDir);
|
|
454
|
-
}
|
|
455
|
-
});
|
|
456
|
-
|
|
457
|
-
claude.on('error', (err: Error) => {
|
|
458
|
-
console.error('[WebSocketImproviseHandler] Failed to spawn Claude for commit:', err);
|
|
459
|
-
ctx.send(ws, { type: 'gitError', tabId, data: { error: 'Failed to generate commit message' } });
|
|
460
|
-
});
|
|
461
|
-
|
|
462
|
-
setTimeout(() => {
|
|
463
|
-
claude.kill();
|
|
464
|
-
}, 30000);
|
|
465
|
-
|
|
242
|
+
const hashResult = await executeGitCommand(['rev-parse', '--short', 'HEAD'], workingDir);
|
|
243
|
+
ctx.send(ws, { type: 'gitCommitted', tabId, data: { hash: hashResult.stdout.trim(), message: commitMessage } });
|
|
244
|
+
handleGitStatus(ctx, ws, tabId, workingDir);
|
|
245
|
+
}
|
|
466
246
|
} catch (error: unknown) {
|
|
467
|
-
ctx
|
|
247
|
+
sendGitError(ctx, ws, tabId, error);
|
|
468
248
|
}
|
|
469
249
|
}
|
|
470
250
|
|
|
@@ -540,7 +320,7 @@ async function handleGitPush(ctx: HandlerContext, ws: WSContext, tabId: string,
|
|
|
540
320
|
|
|
541
321
|
ctx.send(ws, { type: 'gitPushed', tabId, data: { output: result.stdout || result.stderr } });
|
|
542
322
|
} catch (error: unknown) {
|
|
543
|
-
ctx
|
|
323
|
+
sendGitError(ctx, ws, tabId, error);
|
|
544
324
|
}
|
|
545
325
|
}
|
|
546
326
|
|
|
@@ -554,371 +334,6 @@ async function handleGitPull(ctx: HandlerContext, ws: WSContext, tabId: string,
|
|
|
554
334
|
|
|
555
335
|
ctx.send(ws, { type: 'gitPulled', tabId, data: { output: result.stdout || result.stderr } });
|
|
556
336
|
} catch (error: unknown) {
|
|
557
|
-
ctx
|
|
558
|
-
}
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
async function handleGitLog(ctx: HandlerContext, ws: WSContext, msg: WebSocketMessage, tabId: string, workingDir: string): Promise<void> {
|
|
562
|
-
const limit = msg.data?.limit ?? 10;
|
|
563
|
-
|
|
564
|
-
try {
|
|
565
|
-
const result = await executeGitCommand([
|
|
566
|
-
'log',
|
|
567
|
-
`-${limit}`,
|
|
568
|
-
'--format=%H|%h|%s|%an|%aI'
|
|
569
|
-
], workingDir);
|
|
570
|
-
|
|
571
|
-
if (result.exitCode !== 0) {
|
|
572
|
-
ctx.send(ws, { type: 'gitError', tabId, data: { error: result.stderr || result.stdout || 'Failed to get log' } });
|
|
573
|
-
return;
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
const entries: GitLogEntry[] = result.stdout.trim().split('\n').filter(Boolean).map(line => {
|
|
577
|
-
const [hash, shortHash, subject, author, date] = line.split('|');
|
|
578
|
-
const cleanSubject = stripCoauthorLines(subject || '') || subject || '';
|
|
579
|
-
return { hash, shortHash, subject: cleanSubject, author, date };
|
|
580
|
-
});
|
|
581
|
-
|
|
582
|
-
ctx.send(ws, { type: 'gitLog', tabId, data: { entries } });
|
|
583
|
-
} catch (error: unknown) {
|
|
584
|
-
ctx.send(ws, { type: 'gitError', tabId, data: { error: error instanceof Error ? error.message : String(error) } });
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
/** Directories to skip when scanning for git repos */
|
|
589
|
-
const SKIP_DIRS = ['node_modules', 'vendor', '.git'];
|
|
590
|
-
|
|
591
|
-
function shouldSkipDir(name: string): boolean {
|
|
592
|
-
return name.startsWith('.') || SKIP_DIRS.includes(name);
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
async function getRepoBranch(repoPath: string): Promise<string | undefined> {
|
|
596
|
-
const result = await executeGitCommand(['rev-parse', '--abbrev-ref', 'HEAD'], repoPath);
|
|
597
|
-
return result.exitCode === 0 ? result.stdout.trim() : undefined;
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
async function scanForGitRepos(dir: string, depth: number, maxDepth: number, repos: GitRepoInfo[]): Promise<void> {
|
|
601
|
-
if (depth > maxDepth) return;
|
|
602
|
-
|
|
603
|
-
let entries: string[];
|
|
604
|
-
try {
|
|
605
|
-
entries = readdirSync(dir);
|
|
606
|
-
} catch {
|
|
607
|
-
return;
|
|
608
|
-
}
|
|
609
|
-
|
|
610
|
-
for (const name of entries) {
|
|
611
|
-
if (shouldSkipDir(name)) continue;
|
|
612
|
-
|
|
613
|
-
const fullPath = join(dir, name);
|
|
614
|
-
const gitPath = join(fullPath, '.git');
|
|
615
|
-
|
|
616
|
-
if (existsSync(gitPath)) {
|
|
617
|
-
repos.push({ path: fullPath, name, branch: await getRepoBranch(fullPath) });
|
|
618
|
-
} else {
|
|
619
|
-
await scanForGitRepos(fullPath, depth + 1, maxDepth, repos);
|
|
620
|
-
}
|
|
621
|
-
}
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
async function handleGitDiscoverRepos(ctx: HandlerContext, ws: WSContext, tabId: string, workingDir: string): Promise<void> {
|
|
625
|
-
try {
|
|
626
|
-
const repos: GitRepoInfo[] = [];
|
|
627
|
-
const rootIsGitRepo = existsSync(join(workingDir, '.git'));
|
|
628
|
-
|
|
629
|
-
if (rootIsGitRepo) {
|
|
630
|
-
repos.push({
|
|
631
|
-
path: workingDir,
|
|
632
|
-
name: workingDir.split('/').pop() || workingDir,
|
|
633
|
-
branch: await getRepoBranch(workingDir),
|
|
634
|
-
});
|
|
635
|
-
} else {
|
|
636
|
-
await scanForGitRepos(workingDir, 1, 3, repos);
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
const response: GitReposDiscoveredResponse = {
|
|
640
|
-
repos,
|
|
641
|
-
rootIsGitRepo,
|
|
642
|
-
selectedDirectory: ctx.gitDirectories.get(tabId) || null,
|
|
643
|
-
};
|
|
644
|
-
|
|
645
|
-
ctx.send(ws, { type: 'gitReposDiscovered', tabId, data: response });
|
|
646
|
-
} catch (error: unknown) {
|
|
647
|
-
ctx.send(ws, { type: 'gitError', tabId, data: { error: error instanceof Error ? error.message : String(error) } });
|
|
648
|
-
}
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
async function handleGitSetDirectory(ctx: HandlerContext, ws: WSContext, msg: WebSocketMessage, tabId: string, workingDir: string): Promise<void> {
|
|
652
|
-
const directory = msg.data?.directory as string | undefined;
|
|
653
|
-
|
|
654
|
-
if (!directory) {
|
|
655
|
-
ctx.gitDirectories.delete(tabId);
|
|
656
|
-
const response: GitDirectorySetResponse = {
|
|
657
|
-
directory: workingDir,
|
|
658
|
-
isValid: existsSync(join(workingDir, '.git')),
|
|
659
|
-
};
|
|
660
|
-
ctx.send(ws, { type: 'gitDirectorySet', tabId, data: response });
|
|
661
|
-
handleGitStatus(ctx, ws, tabId, workingDir);
|
|
662
|
-
return;
|
|
663
|
-
}
|
|
664
|
-
|
|
665
|
-
const gitPath = join(directory, '.git');
|
|
666
|
-
const isValid = existsSync(gitPath);
|
|
667
|
-
|
|
668
|
-
if (isValid) {
|
|
669
|
-
ctx.gitDirectories.set(tabId, directory);
|
|
670
|
-
}
|
|
671
|
-
|
|
672
|
-
const response: GitDirectorySetResponse = {
|
|
673
|
-
directory,
|
|
674
|
-
isValid,
|
|
675
|
-
};
|
|
676
|
-
|
|
677
|
-
ctx.send(ws, { type: 'gitDirectorySet', tabId, data: response });
|
|
678
|
-
|
|
679
|
-
if (isValid) {
|
|
680
|
-
handleGitStatus(ctx, ws, tabId, directory);
|
|
681
|
-
handleGitLog(ctx, ws, { type: 'gitLog', data: { limit: 5 } }, tabId, directory);
|
|
682
|
-
}
|
|
683
|
-
}
|
|
684
|
-
|
|
685
|
-
async function handleGitListBranches(ctx: HandlerContext, ws: WSContext, tabId: string, workingDir: string): Promise<void> {
|
|
686
|
-
try {
|
|
687
|
-
const result = await executeGitCommand(
|
|
688
|
-
['branch', '-a', '--format=%(refname:short)|%(objectname:short)|%(upstream:short)|%(HEAD)'],
|
|
689
|
-
workingDir
|
|
690
|
-
);
|
|
691
|
-
if (result.exitCode !== 0) {
|
|
692
|
-
ctx.send(ws, { type: 'gitError', tabId, data: { error: result.stderr || 'Failed to list branches' } });
|
|
693
|
-
return;
|
|
694
|
-
}
|
|
695
|
-
|
|
696
|
-
const currentBranchResult = await executeGitCommand(['rev-parse', '--abbrev-ref', 'HEAD'], workingDir);
|
|
697
|
-
const currentBranch = currentBranchResult.stdout.trim() || 'HEAD';
|
|
698
|
-
|
|
699
|
-
const branches: GitBranchEntry[] = result.stdout.trim().split('\n')
|
|
700
|
-
.filter(line => line.trim())
|
|
701
|
-
.map(line => {
|
|
702
|
-
const [name, shortHash, upstream, head] = line.split('|');
|
|
703
|
-
const isRemote = name.includes('/') && (name.startsWith('origin/') || name.includes('remotes/'));
|
|
704
|
-
return {
|
|
705
|
-
name: name.trim(),
|
|
706
|
-
shortHash: shortHash?.trim() || '',
|
|
707
|
-
isRemote,
|
|
708
|
-
isCurrent: head?.trim() === '*',
|
|
709
|
-
upstream: upstream?.trim() || undefined,
|
|
710
|
-
};
|
|
711
|
-
})
|
|
712
|
-
.filter(b => b.name !== 'origin/HEAD');
|
|
713
|
-
|
|
714
|
-
ctx.send(ws, { type: 'gitBranchList', tabId, data: { branches, current: currentBranch } });
|
|
715
|
-
} catch (error: unknown) {
|
|
716
|
-
ctx.send(ws, { type: 'gitError', tabId, data: { error: error instanceof Error ? error.message : String(error) } });
|
|
717
|
-
}
|
|
718
|
-
}
|
|
719
|
-
|
|
720
|
-
async function handleGitCheckout(ctx: HandlerContext, ws: WSContext, msg: WebSocketMessage, tabId: string, workingDir: string): Promise<void> {
|
|
721
|
-
try {
|
|
722
|
-
const { branch, create, startPoint } = msg.data || {};
|
|
723
|
-
if (!branch) {
|
|
724
|
-
ctx.send(ws, { type: 'gitError', tabId, data: { error: 'Branch name is required' } });
|
|
725
|
-
return;
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
const statusResult = await executeGitCommand(['status', '--porcelain'], workingDir);
|
|
729
|
-
if (statusResult.stdout.trim()) {
|
|
730
|
-
ctx.send(ws, { type: 'gitError', tabId, data: { error: 'Commit or stash changes before switching branches' } });
|
|
731
|
-
return;
|
|
732
|
-
}
|
|
733
|
-
|
|
734
|
-
const prevResult = await executeGitCommand(['rev-parse', '--abbrev-ref', 'HEAD'], workingDir);
|
|
735
|
-
const previous = prevResult.stdout.trim();
|
|
736
|
-
|
|
737
|
-
const args = create
|
|
738
|
-
? ['checkout', '-b', branch, ...(startPoint ? [startPoint] : [])]
|
|
739
|
-
: ['checkout', branch];
|
|
740
|
-
|
|
741
|
-
const result = await executeGitCommand(args, workingDir);
|
|
742
|
-
if (result.exitCode !== 0) {
|
|
743
|
-
ctx.send(ws, { type: 'gitError', tabId, data: { error: result.stderr || 'Failed to checkout branch' } });
|
|
744
|
-
return;
|
|
745
|
-
}
|
|
746
|
-
|
|
747
|
-
ctx.send(ws, { type: 'gitCheckedOut', tabId, data: { branch, previous } });
|
|
748
|
-
handleGitStatus(ctx, ws, tabId, workingDir);
|
|
749
|
-
} catch (error: unknown) {
|
|
750
|
-
ctx.send(ws, { type: 'gitError', tabId, data: { error: error instanceof Error ? error.message : String(error) } });
|
|
751
|
-
}
|
|
752
|
-
}
|
|
753
|
-
|
|
754
|
-
async function handleGitCreateBranch(ctx: HandlerContext, ws: WSContext, msg: WebSocketMessage, tabId: string, workingDir: string): Promise<void> {
|
|
755
|
-
try {
|
|
756
|
-
const { name, startPoint, checkout } = msg.data || {};
|
|
757
|
-
if (!name) {
|
|
758
|
-
ctx.send(ws, { type: 'gitError', tabId, data: { error: 'Branch name is required' } });
|
|
759
|
-
return;
|
|
760
|
-
}
|
|
761
|
-
|
|
762
|
-
const args = ['branch', name, ...(startPoint ? [startPoint] : [])];
|
|
763
|
-
const result = await executeGitCommand(args, workingDir);
|
|
764
|
-
if (result.exitCode !== 0) {
|
|
765
|
-
ctx.send(ws, { type: 'gitError', tabId, data: { error: result.stderr || 'Failed to create branch' } });
|
|
766
|
-
return;
|
|
767
|
-
}
|
|
768
|
-
|
|
769
|
-
const hashResult = await executeGitCommand(['rev-parse', '--short', name], workingDir);
|
|
770
|
-
|
|
771
|
-
if (checkout) {
|
|
772
|
-
await executeGitCommand(['checkout', name], workingDir);
|
|
773
|
-
}
|
|
774
|
-
|
|
775
|
-
ctx.send(ws, { type: 'gitBranchCreated', tabId, data: { name, hash: hashResult.stdout.trim() } });
|
|
776
|
-
} catch (error: unknown) {
|
|
777
|
-
ctx.send(ws, { type: 'gitError', tabId, data: { error: error instanceof Error ? error.message : String(error) } });
|
|
778
|
-
}
|
|
779
|
-
}
|
|
780
|
-
|
|
781
|
-
async function handleGitDeleteBranch(ctx: HandlerContext, ws: WSContext, msg: WebSocketMessage, tabId: string, workingDir: string): Promise<void> {
|
|
782
|
-
try {
|
|
783
|
-
const { name, force } = msg.data || {};
|
|
784
|
-
if (!name) {
|
|
785
|
-
ctx.send(ws, { type: 'gitError', tabId, data: { error: 'Branch name is required' } });
|
|
786
|
-
return;
|
|
787
|
-
}
|
|
788
|
-
|
|
789
|
-
const currentResult = await executeGitCommand(['rev-parse', '--abbrev-ref', 'HEAD'], workingDir);
|
|
790
|
-
if (currentResult.stdout.trim() === name) {
|
|
791
|
-
ctx.send(ws, { type: 'gitError', tabId, data: { error: 'Cannot delete the currently checked out branch' } });
|
|
792
|
-
return;
|
|
793
|
-
}
|
|
794
|
-
|
|
795
|
-
const result = await executeGitCommand(['branch', force ? '-D' : '-d', name], workingDir);
|
|
796
|
-
if (result.exitCode !== 0) {
|
|
797
|
-
ctx.send(ws, { type: 'gitError', tabId, data: { error: result.stderr || 'Failed to delete branch' } });
|
|
798
|
-
return;
|
|
799
|
-
}
|
|
800
|
-
|
|
801
|
-
ctx.send(ws, { type: 'gitBranchDeleted', tabId, data: { name } });
|
|
802
|
-
} catch (error: unknown) {
|
|
803
|
-
ctx.send(ws, { type: 'gitError', tabId, data: { error: error instanceof Error ? error.message : String(error) } });
|
|
804
|
-
}
|
|
805
|
-
}
|
|
806
|
-
|
|
807
|
-
async function handleGitDiff(ctx: HandlerContext, ws: WSContext, msg: WebSocketMessage, tabId: string, workingDir: string): Promise<void> {
|
|
808
|
-
try {
|
|
809
|
-
const { path, staged } = msg.data || {};
|
|
810
|
-
if (!path) {
|
|
811
|
-
ctx.send(ws, { type: 'gitError', tabId, data: { error: 'File path is required' } });
|
|
812
|
-
return;
|
|
813
|
-
}
|
|
814
|
-
|
|
815
|
-
const originalResult = await executeGitCommand(['show', `HEAD:${path}`], workingDir);
|
|
816
|
-
const original = originalResult.exitCode === 0 ? originalResult.stdout : '';
|
|
817
|
-
|
|
818
|
-
let modified: string;
|
|
819
|
-
if (staged) {
|
|
820
|
-
const indexResult = await executeGitCommand(['show', `:${path}`], workingDir);
|
|
821
|
-
modified = indexResult.exitCode === 0 ? indexResult.stdout : '';
|
|
822
|
-
} else {
|
|
823
|
-
const fullPath = join(workingDir, path);
|
|
824
|
-
try {
|
|
825
|
-
modified = readFileSync(fullPath, 'utf-8');
|
|
826
|
-
} catch {
|
|
827
|
-
modified = '';
|
|
828
|
-
}
|
|
829
|
-
}
|
|
830
|
-
|
|
831
|
-
ctx.send(ws, {
|
|
832
|
-
type: 'gitDiffResult',
|
|
833
|
-
tabId,
|
|
834
|
-
data: { path, original, modified, staged: !!staged },
|
|
835
|
-
});
|
|
836
|
-
} catch (error: unknown) {
|
|
837
|
-
ctx.send(ws, { type: 'gitError', tabId, data: { error: error instanceof Error ? error.message : String(error) } });
|
|
838
|
-
}
|
|
839
|
-
}
|
|
840
|
-
|
|
841
|
-
async function handleGitListTags(ctx: HandlerContext, ws: WSContext, tabId: string, workingDir: string): Promise<void> {
|
|
842
|
-
try {
|
|
843
|
-
const result = await executeGitCommand(
|
|
844
|
-
['tag', '-l', '--sort=-creatordate', '--format=%(refname:short)|%(objectname:short)|%(creatordate:iso-strict)|%(subject)'],
|
|
845
|
-
workingDir
|
|
846
|
-
);
|
|
847
|
-
if (result.exitCode !== 0) {
|
|
848
|
-
ctx.send(ws, { type: 'gitError', tabId, data: { error: result.stderr || 'Failed to list tags' } });
|
|
849
|
-
return;
|
|
850
|
-
}
|
|
851
|
-
|
|
852
|
-
const tags: GitTagEntry[] = result.stdout.trim().split('\n')
|
|
853
|
-
.filter(line => line.trim())
|
|
854
|
-
.slice(0, 50)
|
|
855
|
-
.map(line => {
|
|
856
|
-
const parts = line.split('|');
|
|
857
|
-
return {
|
|
858
|
-
name: parts[0]?.trim() || '',
|
|
859
|
-
shortHash: parts[1]?.trim() || '',
|
|
860
|
-
date: parts[2]?.trim() || '',
|
|
861
|
-
message: parts[3]?.trim() || '',
|
|
862
|
-
};
|
|
863
|
-
});
|
|
864
|
-
|
|
865
|
-
ctx.send(ws, { type: 'gitTagList', tabId, data: { tags } });
|
|
866
|
-
} catch (error: unknown) {
|
|
867
|
-
ctx.send(ws, { type: 'gitError', tabId, data: { error: error instanceof Error ? error.message : String(error) } });
|
|
868
|
-
}
|
|
869
|
-
}
|
|
870
|
-
|
|
871
|
-
async function handleGitCreateTag(ctx: HandlerContext, ws: WSContext, msg: WebSocketMessage, tabId: string, workingDir: string): Promise<void> {
|
|
872
|
-
try {
|
|
873
|
-
const { name, message, commit } = msg.data || {};
|
|
874
|
-
if (!name) {
|
|
875
|
-
ctx.send(ws, { type: 'gitError', tabId, data: { error: 'Tag name is required' } });
|
|
876
|
-
return;
|
|
877
|
-
}
|
|
878
|
-
|
|
879
|
-
if (/\s/.test(name) || name.includes('..')) {
|
|
880
|
-
ctx.send(ws, { type: 'gitError', tabId, data: { error: 'Invalid tag name: no spaces or ".." allowed' } });
|
|
881
|
-
return;
|
|
882
|
-
}
|
|
883
|
-
|
|
884
|
-
const args = message
|
|
885
|
-
? ['tag', '-a', name, '-m', message, ...(commit ? [commit] : [])]
|
|
886
|
-
: ['tag', name, ...(commit ? [commit] : [])];
|
|
887
|
-
|
|
888
|
-
const result = await executeGitCommand(args, workingDir);
|
|
889
|
-
if (result.exitCode !== 0) {
|
|
890
|
-
ctx.send(ws, { type: 'gitError', tabId, data: { error: result.stderr || 'Failed to create tag' } });
|
|
891
|
-
return;
|
|
892
|
-
}
|
|
893
|
-
|
|
894
|
-
const hashResult = await executeGitCommand(['rev-parse', '--short', name], workingDir);
|
|
895
|
-
ctx.send(ws, { type: 'gitTagCreated', tabId, data: { name, hash: hashResult.stdout.trim() } });
|
|
896
|
-
} catch (error: unknown) {
|
|
897
|
-
ctx.send(ws, { type: 'gitError', tabId, data: { error: error instanceof Error ? error.message : String(error) } });
|
|
898
|
-
}
|
|
899
|
-
}
|
|
900
|
-
|
|
901
|
-
async function handleGitPushTag(ctx: HandlerContext, ws: WSContext, msg: WebSocketMessage, tabId: string, workingDir: string): Promise<void> {
|
|
902
|
-
try {
|
|
903
|
-
const { name, all } = msg.data || {};
|
|
904
|
-
|
|
905
|
-
const args = all
|
|
906
|
-
? ['push', 'origin', '--tags']
|
|
907
|
-
: ['push', 'origin', name];
|
|
908
|
-
|
|
909
|
-
if (!all && !name) {
|
|
910
|
-
ctx.send(ws, { type: 'gitError', tabId, data: { error: 'Tag name is required' } });
|
|
911
|
-
return;
|
|
912
|
-
}
|
|
913
|
-
|
|
914
|
-
const result = await executeGitCommand(args, workingDir);
|
|
915
|
-
if (result.exitCode !== 0) {
|
|
916
|
-
ctx.send(ws, { type: 'gitError', tabId, data: { error: result.stderr || 'Failed to push tag' } });
|
|
917
|
-
return;
|
|
918
|
-
}
|
|
919
|
-
|
|
920
|
-
ctx.send(ws, { type: 'gitTagPushed', tabId, data: { name: name || 'all', output: result.stderr || result.stdout } });
|
|
921
|
-
} catch (error: unknown) {
|
|
922
|
-
ctx.send(ws, { type: 'gitError', tabId, data: { error: error instanceof Error ? error.message : String(error) } });
|
|
337
|
+
sendGitError(ctx, ws, tabId, error);
|
|
923
338
|
}
|
|
924
339
|
}
|