mstro-app 0.5.0 → 0.5.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/LICENSE +129 -190
- package/PRIVACY.md +3 -3
- package/README.md +5 -1
- package/bin/commands/config.js +0 -1
- package/bin/mstro.js +0 -1
- package/bin/postinstall.js +0 -1
- package/dist/server/cli/headless/claude-invoker-process.d.ts.map +1 -1
- package/dist/server/cli/headless/claude-invoker-process.js +0 -1
- package/dist/server/cli/headless/claude-invoker-process.js.map +1 -1
- package/dist/server/cli/headless/claude-invoker-stall.d.ts.map +1 -1
- package/dist/server/cli/headless/claude-invoker-stall.js +0 -1
- package/dist/server/cli/headless/claude-invoker-stall.js.map +1 -1
- package/dist/server/cli/headless/claude-invoker-stream.d.ts.map +1 -1
- package/dist/server/cli/headless/claude-invoker-stream.js +0 -1
- package/dist/server/cli/headless/claude-invoker-stream.js.map +1 -1
- package/dist/server/cli/headless/claude-invoker-tools.d.ts.map +1 -1
- package/dist/server/cli/headless/claude-invoker-tools.js +0 -1
- package/dist/server/cli/headless/claude-invoker-tools.js.map +1 -1
- package/dist/server/cli/headless/claude-invoker.d.ts.map +1 -1
- package/dist/server/cli/headless/claude-invoker.js +0 -1
- package/dist/server/cli/headless/claude-invoker.js.map +1 -1
- package/dist/server/cli/headless/haiku-assessments.d.ts.map +1 -1
- package/dist/server/cli/headless/haiku-assessments.js +0 -1
- package/dist/server/cli/headless/haiku-assessments.js.map +1 -1
- package/dist/server/cli/headless/headless-logger.d.ts.map +1 -1
- package/dist/server/cli/headless/headless-logger.js +0 -1
- package/dist/server/cli/headless/headless-logger.js.map +1 -1
- package/dist/server/cli/headless/index.d.ts.map +1 -1
- package/dist/server/cli/headless/index.js +0 -1
- package/dist/server/cli/headless/index.js.map +1 -1
- package/dist/server/cli/headless/native-timeout-detector.d.ts.map +1 -1
- package/dist/server/cli/headless/native-timeout-detector.js +0 -1
- package/dist/server/cli/headless/native-timeout-detector.js.map +1 -1
- package/dist/server/cli/headless/output-utils.d.ts.map +1 -1
- package/dist/server/cli/headless/output-utils.js +0 -1
- package/dist/server/cli/headless/output-utils.js.map +1 -1
- package/dist/server/cli/headless/prompt-utils.d.ts.map +1 -1
- package/dist/server/cli/headless/prompt-utils.js +0 -1
- package/dist/server/cli/headless/prompt-utils.js.map +1 -1
- package/dist/server/cli/headless/resilient-runner.d.ts.map +1 -1
- package/dist/server/cli/headless/resilient-runner.js +0 -1
- package/dist/server/cli/headless/resilient-runner.js.map +1 -1
- package/dist/server/cli/headless/retry-strategies.d.ts.map +1 -1
- package/dist/server/cli/headless/retry-strategies.js +0 -1
- package/dist/server/cli/headless/retry-strategies.js.map +1 -1
- package/dist/server/cli/headless/runner.d.ts.map +1 -1
- package/dist/server/cli/headless/runner.js +0 -1
- package/dist/server/cli/headless/runner.js.map +1 -1
- package/dist/server/cli/headless/stall-assessor.d.ts.map +1 -1
- package/dist/server/cli/headless/stall-assessor.js +0 -1
- package/dist/server/cli/headless/stall-assessor.js.map +1 -1
- package/dist/server/cli/headless/tool-watchdog.d.ts.map +1 -1
- package/dist/server/cli/headless/tool-watchdog.js +0 -1
- package/dist/server/cli/headless/tool-watchdog.js.map +1 -1
- package/dist/server/cli/headless/types.d.ts.map +1 -1
- package/dist/server/cli/headless/types.js +0 -1
- package/dist/server/cli/headless/types.js.map +1 -1
- package/dist/server/cli/improvisation-attachments.d.ts.map +1 -1
- package/dist/server/cli/improvisation-attachments.js +0 -1
- package/dist/server/cli/improvisation-attachments.js.map +1 -1
- package/dist/server/cli/improvisation-history-store.d.ts.map +1 -1
- package/dist/server/cli/improvisation-history-store.js +0 -1
- package/dist/server/cli/improvisation-history-store.js.map +1 -1
- package/dist/server/cli/improvisation-movements.d.ts.map +1 -1
- package/dist/server/cli/improvisation-movements.js +0 -1
- package/dist/server/cli/improvisation-movements.js.map +1 -1
- package/dist/server/cli/improvisation-output-queue.d.ts.map +1 -1
- package/dist/server/cli/improvisation-output-queue.js +0 -1
- package/dist/server/cli/improvisation-output-queue.js.map +1 -1
- package/dist/server/cli/improvisation-retry.d.ts.map +1 -1
- package/dist/server/cli/improvisation-retry.js +0 -1
- package/dist/server/cli/improvisation-retry.js.map +1 -1
- package/dist/server/cli/improvisation-session-manager.d.ts.map +1 -1
- package/dist/server/cli/improvisation-session-manager.js +0 -1
- package/dist/server/cli/improvisation-session-manager.js.map +1 -1
- package/dist/server/cli/improvisation-types.d.ts.map +1 -1
- package/dist/server/cli/improvisation-types.js +0 -1
- package/dist/server/cli/improvisation-types.js.map +1 -1
- package/dist/server/cli/retry/retry-best-result.d.ts.map +1 -1
- package/dist/server/cli/retry/retry-best-result.js +0 -1
- package/dist/server/cli/retry/retry-best-result.js.map +1 -1
- package/dist/server/cli/retry/retry-context-loss.d.ts.map +1 -1
- package/dist/server/cli/retry/retry-context-loss.js +0 -1
- package/dist/server/cli/retry/retry-context-loss.js.map +1 -1
- package/dist/server/cli/retry/retry-premature-completion.d.ts.map +1 -1
- package/dist/server/cli/retry/retry-premature-completion.js +1 -2
- package/dist/server/cli/retry/retry-premature-completion.js.map +1 -1
- package/dist/server/cli/retry/retry-recovery-strategies.d.ts.map +1 -1
- package/dist/server/cli/retry/retry-recovery-strategies.js +0 -1
- package/dist/server/cli/retry/retry-recovery-strategies.js.map +1 -1
- package/dist/server/cli/retry/retry-resume-strategy.d.ts.map +1 -1
- package/dist/server/cli/retry/retry-resume-strategy.js +0 -1
- package/dist/server/cli/retry/retry-resume-strategy.js.map +1 -1
- package/dist/server/cli/retry/retry-runner-factory.d.ts.map +1 -1
- package/dist/server/cli/retry/retry-runner-factory.js +0 -1
- package/dist/server/cli/retry/retry-runner-factory.js.map +1 -1
- package/dist/server/cli/retry/retry-tool-results.d.ts.map +1 -1
- package/dist/server/cli/retry/retry-tool-results.js +0 -1
- package/dist/server/cli/retry/retry-tool-results.js.map +1 -1
- package/dist/server/cli/retry/retry-types.d.ts.map +1 -1
- package/dist/server/cli/retry/retry-types.js +0 -1
- package/dist/server/cli/retry/retry-types.js.map +1 -1
- package/dist/server/index.js +0 -1
- package/dist/server/index.js.map +1 -1
- package/dist/server/mcp/bouncer-cli.js +0 -1
- package/dist/server/mcp/bouncer-cli.js.map +1 -1
- package/dist/server/mcp/bouncer-haiku.d.ts.map +1 -1
- package/dist/server/mcp/bouncer-haiku.js +0 -1
- package/dist/server/mcp/bouncer-haiku.js.map +1 -1
- package/dist/server/mcp/bouncer-integration.d.ts.map +1 -1
- package/dist/server/mcp/bouncer-integration.js +0 -1
- package/dist/server/mcp/bouncer-integration.js.map +1 -1
- package/dist/server/mcp/security-analysis.d.ts.map +1 -1
- package/dist/server/mcp/security-analysis.js +0 -1
- package/dist/server/mcp/security-analysis.js.map +1 -1
- package/dist/server/mcp/security-audit.d.ts.map +1 -1
- package/dist/server/mcp/security-audit.js +0 -1
- package/dist/server/mcp/security-audit.js.map +1 -1
- package/dist/server/mcp/security-patterns.d.ts.map +1 -1
- package/dist/server/mcp/security-patterns.js +0 -1
- package/dist/server/mcp/security-patterns.js.map +1 -1
- package/dist/server/mcp/server.js +0 -1
- package/dist/server/mcp/server.js.map +1 -1
- package/dist/server/routes/files.d.ts.map +1 -1
- package/dist/server/routes/files.js +0 -1
- package/dist/server/routes/files.js.map +1 -1
- package/dist/server/routes/improvise.d.ts.map +1 -1
- package/dist/server/routes/improvise.js +0 -1
- package/dist/server/routes/improvise.js.map +1 -1
- package/dist/server/routes/index.d.ts.map +1 -1
- package/dist/server/routes/index.js +0 -1
- package/dist/server/routes/index.js.map +1 -1
- package/dist/server/routes/instances.d.ts.map +1 -1
- package/dist/server/routes/instances.js +0 -1
- package/dist/server/routes/instances.js.map +1 -1
- package/dist/server/routes/notifications.d.ts.map +1 -1
- package/dist/server/routes/notifications.js +0 -1
- package/dist/server/routes/notifications.js.map +1 -1
- package/dist/server/server-setup.d.ts.map +1 -1
- package/dist/server/server-setup.js +0 -1
- package/dist/server/server-setup.js.map +1 -1
- package/dist/server/services/analytics.d.ts.map +1 -1
- package/dist/server/services/analytics.js +0 -1
- package/dist/server/services/analytics.js.map +1 -1
- package/dist/server/services/auth.d.ts.map +1 -1
- package/dist/server/services/auth.js +0 -1
- package/dist/server/services/auth.js.map +1 -1
- package/dist/server/services/client-id.d.ts.map +1 -1
- package/dist/server/services/client-id.js +0 -1
- package/dist/server/services/client-id.js.map +1 -1
- package/dist/server/services/file-explorer-ops.d.ts.map +1 -1
- package/dist/server/services/file-explorer-ops.js +0 -1
- package/dist/server/services/file-explorer-ops.js.map +1 -1
- package/dist/server/services/files.d.ts.map +1 -1
- package/dist/server/services/files.js +0 -1
- package/dist/server/services/files.js.map +1 -1
- package/dist/server/services/instances.d.ts.map +1 -1
- package/dist/server/services/instances.js +0 -1
- package/dist/server/services/instances.js.map +1 -1
- package/dist/server/services/pathUtils.d.ts.map +1 -1
- package/dist/server/services/pathUtils.js +0 -1
- package/dist/server/services/pathUtils.js.map +1 -1
- package/dist/server/services/plan/agent-loader.d.ts.map +1 -1
- package/dist/server/services/plan/agent-loader.js +0 -1
- package/dist/server/services/plan/agent-loader.js.map +1 -1
- package/dist/server/services/plan/board-config.d.ts.map +1 -1
- package/dist/server/services/plan/board-config.js +0 -1
- package/dist/server/services/plan/board-config.js.map +1 -1
- package/dist/server/services/plan/composer.d.ts.map +1 -1
- package/dist/server/services/plan/composer.js +0 -1
- package/dist/server/services/plan/composer.js.map +1 -1
- package/dist/server/services/plan/config-installer.d.ts.map +1 -1
- package/dist/server/services/plan/config-installer.js +0 -1
- package/dist/server/services/plan/config-installer.js.map +1 -1
- package/dist/server/services/plan/dependency-resolver.d.ts.map +1 -1
- package/dist/server/services/plan/dependency-resolver.js +0 -1
- package/dist/server/services/plan/dependency-resolver.js.map +1 -1
- package/dist/server/services/plan/executor.d.ts.map +1 -1
- package/dist/server/services/plan/executor.js +45 -3
- package/dist/server/services/plan/executor.js.map +1 -1
- package/dist/server/services/plan/front-matter.d.ts.map +1 -1
- package/dist/server/services/plan/front-matter.js +0 -1
- package/dist/server/services/plan/front-matter.js.map +1 -1
- package/dist/server/services/plan/issue-classification.d.ts.map +1 -1
- package/dist/server/services/plan/issue-classification.js +0 -1
- package/dist/server/services/plan/issue-classification.js.map +1 -1
- package/dist/server/services/plan/issue-loader.d.ts.map +1 -1
- package/dist/server/services/plan/issue-loader.js +0 -1
- package/dist/server/services/plan/issue-loader.js.map +1 -1
- package/dist/server/services/plan/issue-prompt-builder.d.ts.map +1 -1
- package/dist/server/services/plan/issue-prompt-builder.js +0 -1
- package/dist/server/services/plan/issue-prompt-builder.js.map +1 -1
- package/dist/server/services/plan/issue-retry.d.ts +3 -1
- package/dist/server/services/plan/issue-retry.d.ts.map +1 -1
- package/dist/server/services/plan/issue-retry.js +2 -1
- package/dist/server/services/plan/issue-retry.js.map +1 -1
- package/dist/server/services/plan/issue-writer.d.ts.map +1 -1
- package/dist/server/services/plan/issue-writer.js +0 -1
- package/dist/server/services/plan/issue-writer.js.map +1 -1
- package/dist/server/services/plan/output-manager.d.ts.map +1 -1
- package/dist/server/services/plan/output-manager.js +0 -1
- package/dist/server/services/plan/output-manager.js.map +1 -1
- package/dist/server/services/plan/parser-core.d.ts.map +1 -1
- package/dist/server/services/plan/parser-core.js +0 -1
- package/dist/server/services/plan/parser-core.js.map +1 -1
- package/dist/server/services/plan/parser-migration.d.ts.map +1 -1
- package/dist/server/services/plan/parser-migration.js +0 -1
- package/dist/server/services/plan/parser-migration.js.map +1 -1
- package/dist/server/services/plan/parser.d.ts.map +1 -1
- package/dist/server/services/plan/parser.js +0 -1
- package/dist/server/services/plan/parser.js.map +1 -1
- package/dist/server/services/plan/progress-log.d.ts.map +1 -1
- package/dist/server/services/plan/progress-log.js +0 -1
- package/dist/server/services/plan/progress-log.js.map +1 -1
- package/dist/server/services/plan/prompt-builder.d.ts.map +1 -1
- package/dist/server/services/plan/prompt-builder.js +0 -1
- package/dist/server/services/plan/prompt-builder.js.map +1 -1
- package/dist/server/services/plan/readiness-planner.d.ts.map +1 -1
- package/dist/server/services/plan/readiness-planner.js +0 -1
- package/dist/server/services/plan/readiness-planner.js.map +1 -1
- package/dist/server/services/plan/review-gate.d.ts.map +1 -1
- package/dist/server/services/plan/review-gate.js +0 -1
- package/dist/server/services/plan/review-gate.js.map +1 -1
- package/dist/server/services/plan/state-reconciler.d.ts.map +1 -1
- package/dist/server/services/plan/state-reconciler.js +0 -1
- package/dist/server/services/plan/state-reconciler.js.map +1 -1
- package/dist/server/services/plan/types.d.ts.map +1 -1
- package/dist/server/services/plan/types.js +0 -1
- package/dist/server/services/plan/types.js.map +1 -1
- package/dist/server/services/plan/watcher.d.ts.map +1 -1
- package/dist/server/services/plan/watcher.js +0 -1
- package/dist/server/services/plan/watcher.js.map +1 -1
- package/dist/server/services/platform-credentials.d.ts.map +1 -1
- package/dist/server/services/platform-credentials.js +0 -1
- package/dist/server/services/platform-credentials.js.map +1 -1
- package/dist/server/services/platform-token-lifecycle.d.ts +70 -0
- package/dist/server/services/platform-token-lifecycle.d.ts.map +1 -0
- package/dist/server/services/platform-token-lifecycle.js +156 -0
- package/dist/server/services/platform-token-lifecycle.js.map +1 -0
- package/dist/server/services/platform.d.ts +21 -56
- package/dist/server/services/platform.d.ts.map +1 -1
- package/dist/server/services/platform.js +98 -142
- package/dist/server/services/platform.js.map +1 -1
- package/dist/server/services/sentry.d.ts.map +1 -1
- package/dist/server/services/sentry.js +0 -1
- package/dist/server/services/sentry.js.map +1 -1
- package/dist/server/services/settings.d.ts.map +1 -1
- package/dist/server/services/settings.js +0 -1
- package/dist/server/services/settings.js.map +1 -1
- package/dist/server/services/terminal/pty-manager.d.ts.map +1 -1
- package/dist/server/services/terminal/pty-manager.js +0 -1
- package/dist/server/services/terminal/pty-manager.js.map +1 -1
- package/dist/server/services/terminal/pty-utils.d.ts.map +1 -1
- package/dist/server/services/terminal/pty-utils.js +0 -1
- package/dist/server/services/terminal/pty-utils.js.map +1 -1
- package/dist/server/services/websocket/autocomplete.d.ts.map +1 -1
- package/dist/server/services/websocket/autocomplete.js +0 -1
- package/dist/server/services/websocket/autocomplete.js.map +1 -1
- package/dist/server/services/websocket/file-definition-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/file-definition-handlers.js +0 -1
- package/dist/server/services/websocket/file-definition-handlers.js.map +1 -1
- package/dist/server/services/websocket/file-download-handler.d.ts.map +1 -1
- package/dist/server/services/websocket/file-download-handler.js +0 -1
- package/dist/server/services/websocket/file-download-handler.js.map +1 -1
- package/dist/server/services/websocket/file-explorer-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/file-explorer-handlers.js +0 -1
- package/dist/server/services/websocket/file-explorer-handlers.js.map +1 -1
- package/dist/server/services/websocket/file-search-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/file-search-handlers.js +0 -1
- package/dist/server/services/websocket/file-search-handlers.js.map +1 -1
- package/dist/server/services/websocket/file-upload-handler.d.ts +2 -3
- package/dist/server/services/websocket/file-upload-handler.d.ts.map +1 -1
- package/dist/server/services/websocket/file-upload-handler.js +4 -7
- package/dist/server/services/websocket/file-upload-handler.js.map +1 -1
- package/dist/server/services/websocket/file-utils.d.ts.map +1 -1
- package/dist/server/services/websocket/file-utils.js +0 -1
- package/dist/server/services/websocket/file-utils.js.map +1 -1
- package/dist/server/services/websocket/git-branch-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/git-branch-handlers.js +0 -1
- package/dist/server/services/websocket/git-branch-handlers.js.map +1 -1
- package/dist/server/services/websocket/git-diff-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/git-diff-handlers.js +0 -1
- package/dist/server/services/websocket/git-diff-handlers.js.map +1 -1
- package/dist/server/services/websocket/git-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/git-handlers.js +58 -6
- package/dist/server/services/websocket/git-handlers.js.map +1 -1
- package/dist/server/services/websocket/git-head-watcher.d.ts.map +1 -1
- package/dist/server/services/websocket/git-head-watcher.js +0 -1
- package/dist/server/services/websocket/git-head-watcher.js.map +1 -1
- package/dist/server/services/websocket/git-log-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/git-log-handlers.js +0 -1
- package/dist/server/services/websocket/git-log-handlers.js.map +1 -1
- package/dist/server/services/websocket/git-pr-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/git-pr-handlers.js +0 -1
- package/dist/server/services/websocket/git-pr-handlers.js.map +1 -1
- package/dist/server/services/websocket/git-tag-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/git-tag-handlers.js +0 -1
- package/dist/server/services/websocket/git-tag-handlers.js.map +1 -1
- package/dist/server/services/websocket/git-utils.d.ts +18 -3
- package/dist/server/services/websocket/git-utils.d.ts.map +1 -1
- package/dist/server/services/websocket/git-utils.js +58 -8
- package/dist/server/services/websocket/git-utils.js.map +1 -1
- package/dist/server/services/websocket/git-worktree-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/git-worktree-handlers.js +230 -14
- package/dist/server/services/websocket/git-worktree-handlers.js.map +1 -1
- package/dist/server/services/websocket/handler-context.d.ts.map +1 -1
- package/dist/server/services/websocket/handler-context.js +0 -1
- package/dist/server/services/websocket/handler-context.js.map +1 -1
- package/dist/server/services/websocket/handler.d.ts.map +1 -1
- package/dist/server/services/websocket/handler.js +3 -4
- package/dist/server/services/websocket/handler.js.map +1 -1
- package/dist/server/services/websocket/index.d.ts.map +1 -1
- package/dist/server/services/websocket/index.js +0 -1
- package/dist/server/services/websocket/index.js.map +1 -1
- package/dist/server/services/websocket/msg-id-tracker.d.ts.map +1 -1
- package/dist/server/services/websocket/msg-id-tracker.js +0 -1
- package/dist/server/services/websocket/msg-id-tracker.js.map +1 -1
- package/dist/server/services/websocket/plan-board-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/plan-board-handlers.js +0 -1
- package/dist/server/services/websocket/plan-board-handlers.js.map +1 -1
- package/dist/server/services/websocket/plan-execution-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/plan-execution-handlers.js +6 -2
- package/dist/server/services/websocket/plan-execution-handlers.js.map +1 -1
- package/dist/server/services/websocket/plan-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/plan-handlers.js +0 -1
- package/dist/server/services/websocket/plan-handlers.js.map +1 -1
- package/dist/server/services/websocket/plan-helpers.d.ts.map +1 -1
- package/dist/server/services/websocket/plan-helpers.js +0 -1
- package/dist/server/services/websocket/plan-helpers.js.map +1 -1
- package/dist/server/services/websocket/plan-issue-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/plan-issue-handlers.js +0 -1
- package/dist/server/services/websocket/plan-issue-handlers.js.map +1 -1
- package/dist/server/services/websocket/plan-sprint-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/plan-sprint-handlers.js +0 -1
- package/dist/server/services/websocket/plan-sprint-handlers.js.map +1 -1
- package/dist/server/services/websocket/quality-complexity.d.ts.map +1 -1
- package/dist/server/services/websocket/quality-complexity.js +0 -1
- package/dist/server/services/websocket/quality-complexity.js.map +1 -1
- package/dist/server/services/websocket/quality-grading.d.ts +46 -0
- package/dist/server/services/websocket/quality-grading.d.ts.map +1 -0
- package/dist/server/services/websocket/quality-grading.js +482 -0
- package/dist/server/services/websocket/quality-grading.js.map +1 -0
- package/dist/server/services/websocket/quality-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/quality-handlers.js +0 -1
- package/dist/server/services/websocket/quality-handlers.js.map +1 -1
- package/dist/server/services/websocket/quality-linting.d.ts.map +1 -1
- package/dist/server/services/websocket/quality-linting.js +0 -1
- package/dist/server/services/websocket/quality-linting.js.map +1 -1
- package/dist/server/services/websocket/quality-persistence.d.ts +14 -0
- package/dist/server/services/websocket/quality-persistence.d.ts.map +1 -1
- package/dist/server/services/websocket/quality-persistence.js +28 -12
- package/dist/server/services/websocket/quality-persistence.js.map +1 -1
- package/dist/server/services/websocket/quality-review-agent.d.ts.map +1 -1
- package/dist/server/services/websocket/quality-review-agent.js +0 -1
- package/dist/server/services/websocket/quality-review-agent.js.map +1 -1
- package/dist/server/services/websocket/quality-service.d.ts +3 -1
- package/dist/server/services/websocket/quality-service.d.ts.map +1 -1
- package/dist/server/services/websocket/quality-service.js +53 -58
- package/dist/server/services/websocket/quality-service.js.map +1 -1
- package/dist/server/services/websocket/quality-tools.d.ts +1 -1
- package/dist/server/services/websocket/quality-tools.d.ts.map +1 -1
- package/dist/server/services/websocket/quality-tools.js +6 -3
- package/dist/server/services/websocket/quality-tools.js.map +1 -1
- package/dist/server/services/websocket/quality-types.d.ts +18 -2
- package/dist/server/services/websocket/quality-types.d.ts.map +1 -1
- package/dist/server/services/websocket/quality-types.js +0 -1
- package/dist/server/services/websocket/quality-types.js.map +1 -1
- package/dist/server/services/websocket/session-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/session-handlers.js +0 -1
- package/dist/server/services/websocket/session-handlers.js.map +1 -1
- package/dist/server/services/websocket/session-history.d.ts.map +1 -1
- package/dist/server/services/websocket/session-history.js +0 -1
- package/dist/server/services/websocket/session-history.js.map +1 -1
- package/dist/server/services/websocket/session-initialization.d.ts.map +1 -1
- package/dist/server/services/websocket/session-initialization.js +0 -1
- package/dist/server/services/websocket/session-initialization.js.map +1 -1
- package/dist/server/services/websocket/session-registry.d.ts.map +1 -1
- package/dist/server/services/websocket/session-registry.js +0 -1
- package/dist/server/services/websocket/session-registry.js.map +1 -1
- package/dist/server/services/websocket/settings-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/settings-handlers.js +0 -1
- package/dist/server/services/websocket/settings-handlers.js.map +1 -1
- package/dist/server/services/websocket/skill-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/skill-handlers.js +0 -1
- package/dist/server/services/websocket/skill-handlers.js.map +1 -1
- package/dist/server/services/websocket/skill-watcher.d.ts.map +1 -1
- package/dist/server/services/websocket/skill-watcher.js +0 -1
- package/dist/server/services/websocket/skill-watcher.js.map +1 -1
- package/dist/server/services/websocket/tab-broadcast.d.ts.map +1 -1
- package/dist/server/services/websocket/tab-broadcast.js +0 -1
- package/dist/server/services/websocket/tab-broadcast.js.map +1 -1
- package/dist/server/services/websocket/tab-event-buffer.d.ts.map +1 -1
- package/dist/server/services/websocket/tab-event-buffer.js +0 -1
- package/dist/server/services/websocket/tab-event-buffer.js.map +1 -1
- package/dist/server/services/websocket/tab-event-replay.d.ts.map +1 -1
- package/dist/server/services/websocket/tab-event-replay.js +0 -1
- package/dist/server/services/websocket/tab-event-replay.js.map +1 -1
- package/dist/server/services/websocket/tab-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/tab-handlers.js +0 -1
- package/dist/server/services/websocket/tab-handlers.js.map +1 -1
- package/dist/server/services/websocket/terminal-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/terminal-handlers.js +39 -4
- package/dist/server/services/websocket/terminal-handlers.js.map +1 -1
- package/dist/server/services/websocket/types.d.ts +2 -2
- package/dist/server/services/websocket/types.d.ts.map +1 -1
- package/dist/server/services/websocket/types.js +2 -3
- package/dist/server/services/websocket/types.js.map +1 -1
- package/dist/server/utils/agent-manager.d.ts.map +1 -1
- package/dist/server/utils/agent-manager.js +0 -1
- package/dist/server/utils/agent-manager.js.map +1 -1
- package/dist/server/utils/paths.d.ts.map +1 -1
- package/dist/server/utils/paths.js +0 -1
- package/dist/server/utils/paths.js.map +1 -1
- package/dist/server/utils/port-manager.d.ts.map +1 -1
- package/dist/server/utils/port-manager.js +0 -1
- package/dist/server/utils/port-manager.js.map +1 -1
- package/dist/server/utils/port.d.ts.map +1 -1
- package/dist/server/utils/port.js +0 -1
- package/dist/server/utils/port.js.map +1 -1
- package/package.json +2 -2
- package/server/cli/headless/claude-invoker-process.ts +0 -1
- package/server/cli/headless/claude-invoker-stall.ts +0 -1
- package/server/cli/headless/claude-invoker-stream.ts +0 -1
- package/server/cli/headless/claude-invoker-tools.ts +0 -1
- package/server/cli/headless/claude-invoker.ts +0 -1
- package/server/cli/headless/haiku-assessments.ts +0 -1
- package/server/cli/headless/headless-logger.ts +0 -1
- package/server/cli/headless/index.ts +0 -1
- package/server/cli/headless/native-timeout-detector.ts +0 -1
- package/server/cli/headless/output-utils.ts +0 -1
- package/server/cli/headless/prompt-utils.ts +0 -1
- package/server/cli/headless/resilient-runner.ts +0 -1
- package/server/cli/headless/retry-strategies.ts +0 -1
- package/server/cli/headless/runner.ts +0 -1
- package/server/cli/headless/stall-assessor.ts +0 -1
- package/server/cli/headless/tool-watchdog.ts +0 -1
- package/server/cli/headless/types.ts +0 -1
- package/server/cli/improvisation-attachments.ts +0 -1
- package/server/cli/improvisation-history-store.ts +0 -1
- package/server/cli/improvisation-movements.ts +0 -1
- package/server/cli/improvisation-output-queue.ts +0 -1
- package/server/cli/improvisation-retry.ts +0 -1
- package/server/cli/improvisation-session-manager.ts +0 -1
- package/server/cli/improvisation-types.ts +0 -1
- package/server/cli/retry/retry-best-result.ts +0 -1
- package/server/cli/retry/retry-context-loss.ts +0 -1
- package/server/cli/retry/retry-premature-completion.ts +1 -2
- package/server/cli/retry/retry-recovery-strategies.ts +0 -1
- package/server/cli/retry/retry-resume-strategy.ts +0 -1
- package/server/cli/retry/retry-runner-factory.ts +0 -1
- package/server/cli/retry/retry-tool-results.ts +0 -1
- package/server/cli/retry/retry-types.ts +0 -1
- package/server/index.ts +0 -1
- package/server/mcp/bouncer-cli.ts +0 -1
- package/server/mcp/bouncer-haiku.ts +0 -1
- package/server/mcp/bouncer-integration.ts +0 -1
- package/server/mcp/security-analysis.ts +0 -1
- package/server/mcp/security-audit.ts +0 -1
- package/server/mcp/security-patterns.ts +0 -1
- package/server/mcp/server.ts +0 -1
- package/server/routes/files.ts +0 -1
- package/server/routes/improvise.ts +0 -1
- package/server/routes/index.ts +0 -1
- package/server/routes/instances.ts +0 -1
- package/server/routes/notifications.ts +0 -1
- package/server/server-setup.ts +0 -1
- package/server/services/analytics.ts +0 -1
- package/server/services/auth.ts +0 -1
- package/server/services/client-id.ts +0 -1
- package/server/services/file-explorer-ops.ts +0 -1
- package/server/services/files.ts +0 -1
- package/server/services/instances.ts +0 -1
- package/server/services/pathUtils.ts +0 -1
- package/server/services/plan/agent-loader.ts +0 -1
- package/server/services/plan/agents/code-review.md +13 -11
- package/server/services/plan/board-config.ts +0 -1
- package/server/services/plan/composer.ts +0 -1
- package/server/services/plan/config-installer.ts +0 -1
- package/server/services/plan/dependency-resolver.ts +0 -1
- package/server/services/plan/executor.ts +45 -3
- package/server/services/plan/front-matter.ts +0 -1
- package/server/services/plan/issue-classification.ts +0 -1
- package/server/services/plan/issue-loader.ts +0 -1
- package/server/services/plan/issue-prompt-builder.ts +0 -1
- package/server/services/plan/issue-retry.ts +5 -2
- package/server/services/plan/issue-writer.ts +0 -1
- package/server/services/plan/output-manager.ts +0 -1
- package/server/services/plan/parser-core.ts +0 -1
- package/server/services/plan/parser-migration.ts +0 -1
- package/server/services/plan/parser.ts +0 -1
- package/server/services/plan/progress-log.ts +0 -1
- package/server/services/plan/prompt-builder.ts +0 -1
- package/server/services/plan/readiness-planner.ts +0 -1
- package/server/services/plan/review-gate.ts +0 -1
- package/server/services/plan/state-reconciler.ts +0 -1
- package/server/services/plan/types.ts +0 -1
- package/server/services/plan/watcher.ts +0 -1
- package/server/services/platform-credentials.ts +0 -1
- package/server/services/platform-token-lifecycle.ts +171 -0
- package/server/services/platform.ts +106 -148
- package/server/services/sentry.ts +0 -1
- package/server/services/settings.ts +0 -1
- package/server/services/terminal/pty-manager.ts +0 -1
- package/server/services/terminal/pty-utils.ts +0 -1
- package/server/services/websocket/autocomplete.ts +0 -1
- package/server/services/websocket/file-definition-handlers.ts +0 -1
- package/server/services/websocket/file-download-handler.ts +0 -1
- package/server/services/websocket/file-explorer-handlers.ts +0 -1
- package/server/services/websocket/file-search-handlers.ts +0 -1
- package/server/services/websocket/file-upload-handler.ts +6 -5
- package/server/services/websocket/file-utils.ts +0 -1
- package/server/services/websocket/git-branch-handlers.ts +0 -1
- package/server/services/websocket/git-diff-handlers.ts +0 -1
- package/server/services/websocket/git-handlers.ts +66 -10
- package/server/services/websocket/git-head-watcher.ts +0 -1
- package/server/services/websocket/git-log-handlers.ts +0 -1
- package/server/services/websocket/git-pr-handlers.ts +0 -1
- package/server/services/websocket/git-tag-handlers.ts +0 -1
- package/server/services/websocket/git-utils.ts +69 -9
- package/server/services/websocket/git-worktree-handlers.ts +260 -17
- package/server/services/websocket/handler-context.ts +0 -1
- package/server/services/websocket/handler.ts +3 -4
- package/server/services/websocket/index.ts +0 -1
- package/server/services/websocket/msg-id-tracker.ts +0 -1
- package/server/services/websocket/plan-board-handlers.ts +0 -1
- package/server/services/websocket/plan-execution-handlers.ts +6 -2
- package/server/services/websocket/plan-handlers.ts +0 -1
- package/server/services/websocket/plan-helpers.ts +0 -1
- package/server/services/websocket/plan-issue-handlers.ts +0 -1
- package/server/services/websocket/plan-sprint-handlers.ts +0 -1
- package/server/services/websocket/quality-complexity.ts +0 -1
- package/server/services/websocket/quality-grading.ts +611 -0
- package/server/services/websocket/quality-handlers.ts +0 -1
- package/server/services/websocket/quality-linting.ts +0 -1
- package/server/services/websocket/quality-persistence.ts +30 -8
- package/server/services/websocket/quality-review-agent.ts +0 -1
- package/server/services/websocket/quality-service.ts +54 -55
- package/server/services/websocket/quality-tools.ts +11 -3
- package/server/services/websocket/quality-types.ts +21 -3
- package/server/services/websocket/session-handlers.ts +0 -1
- package/server/services/websocket/session-history.ts +0 -1
- package/server/services/websocket/session-initialization.ts +0 -1
- package/server/services/websocket/session-registry.ts +0 -1
- package/server/services/websocket/settings-handlers.ts +0 -1
- package/server/services/websocket/skill-handlers.ts +0 -1
- package/server/services/websocket/skill-watcher.ts +0 -1
- package/server/services/websocket/tab-broadcast.ts +0 -1
- package/server/services/websocket/tab-event-buffer.ts +0 -1
- package/server/services/websocket/tab-event-replay.ts +0 -1
- package/server/services/websocket/tab-handlers.ts +0 -1
- package/server/services/websocket/terminal-handlers.ts +39 -3
- package/server/services/websocket/types.ts +2 -3
- package/server/utils/agent-manager.ts +0 -1
- package/server/utils/paths.ts +0 -1
- package/server/utils/port-manager.ts +0 -1
- package/server/utils/port.ts +0 -1
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
// Copyright (c) 2025-present Mstro, Inc. All rights reserved.
|
|
2
|
-
// Licensed under the MIT License. See LICENSE file for details.
|
|
3
2
|
|
|
4
3
|
import { resolve } from 'node:path';
|
|
5
4
|
import { loadSkillPrompt } from '../plan/agent-loader.js';
|
|
@@ -8,7 +7,7 @@ import { handleGitCommitDiff, handleGitDiff, handleGitShowCommit } from './git-d
|
|
|
8
7
|
import { handleGitDiscoverRepos, handleGitLog, handleGitSetDirectory } from './git-log-handlers.js';
|
|
9
8
|
import { handleGitPRMessage } from './git-pr-handlers.js';
|
|
10
9
|
import { handleGitCreateTag, handleGitListTags, handleGitPushTag } from './git-tag-handlers.js';
|
|
11
|
-
import { executeGitCommand, parseGitStatus, sendGitError, spawnHaikuWithPrompt, stripCoauthorLines, truncateDiff } from './git-utils.js';
|
|
10
|
+
import { classifyHaikuFailure, executeGitCommand, type HaikuResult, logCommitMessageEvent, parseGitStatus, sendGitError, spawnHaikuWithPrompt, stripCoauthorLines, truncateDiff } from './git-utils.js';
|
|
12
11
|
import { handleGitWorktreeMessage } from './git-worktree-handlers.js';
|
|
13
12
|
import type { HandlerContext } from './handler-context.js';
|
|
14
13
|
import type { GitStatusResponse, WebSocketMessage, WSContext } from './types.js';
|
|
@@ -200,7 +199,50 @@ async function handleGitCommit(ctx: HandlerContext, ws: WSContext, msg: WebSocke
|
|
|
200
199
|
}
|
|
201
200
|
}
|
|
202
201
|
|
|
202
|
+
/** Tabs currently generating an AI commit message. Prevents double-clicks
|
|
203
|
+
* from spawning two `claude` subprocesses for the same tab and clobbering
|
|
204
|
+
* the UI when the second response arrives. Module-scoped so it survives
|
|
205
|
+
* HandlerContext lifecycle (one process per CLI machine). */
|
|
206
|
+
const inflightCommitMessage = new Set<string>();
|
|
207
|
+
|
|
208
|
+
const haikuSucceeded = (r: HaikuResult): boolean => r.exitCode === 0 && r.stdout.trim().length > 0;
|
|
209
|
+
// rate_limit is terminal because a 500ms retry hits the same window the API
|
|
210
|
+
// expects you to wait ~60s on; retrying just doubles user-perceived latency
|
|
211
|
+
// before we surface the "wait a minute" message.
|
|
212
|
+
const RETRY_TERMINAL_REASONS = new Set(['binary_missing', 'auth', 'rate_limit']);
|
|
213
|
+
|
|
214
|
+
/** Run Haiku with one retry on transient failures. Logs each attempt. */
|
|
215
|
+
async function runHaikuForCommitMessage(
|
|
216
|
+
prompt: string, systemPrompt: string, workingDir: string,
|
|
217
|
+
meta: { tabId: string; startedAt: number; diffBytes: number },
|
|
218
|
+
): Promise<{ result: HaikuResult; attempts: number }> {
|
|
219
|
+
let result = await spawnHaikuWithPrompt(prompt, systemPrompt, workingDir);
|
|
220
|
+
if (haikuSucceeded(result)) return { result, attempts: 1 };
|
|
221
|
+
|
|
222
|
+
const first = classifyHaikuFailure(result);
|
|
223
|
+
if (RETRY_TERMINAL_REASONS.has(first.reason)) return { result, attempts: 1 };
|
|
224
|
+
|
|
225
|
+
logCommitMessageEvent(workingDir, {
|
|
226
|
+
tabId: meta.tabId, attempt: 1, success: false, reason: first.reason,
|
|
227
|
+
exitCode: result.exitCode, timedOut: result.timedOut,
|
|
228
|
+
stderrTail: (result.stderr || '').trim().slice(-500),
|
|
229
|
+
latencyMs: Date.now() - meta.startedAt, diffBytes: meta.diffBytes, willRetry: true,
|
|
230
|
+
});
|
|
231
|
+
await new Promise(r => setTimeout(r, 500));
|
|
232
|
+
result = await spawnHaikuWithPrompt(prompt, systemPrompt, workingDir);
|
|
233
|
+
return { result, attempts: 2 };
|
|
234
|
+
}
|
|
235
|
+
|
|
203
236
|
async function handleGitCommitWithAI(ctx: HandlerContext, ws: WSContext, msg: WebSocketMessage, tabId: string, workingDir: string): Promise<void> {
|
|
237
|
+
if (inflightCommitMessage.has(tabId)) {
|
|
238
|
+
// Drop the duplicate request silently — the user already has one in
|
|
239
|
+
// flight. Surfacing "already generating" as a toast is noisier than
|
|
240
|
+
// just ignoring the click.
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
inflightCommitMessage.add(tabId);
|
|
244
|
+
const startedAt = Date.now();
|
|
245
|
+
|
|
204
246
|
try {
|
|
205
247
|
const statusResult = await executeGitCommand(['status', '--porcelain=v1'], workingDir);
|
|
206
248
|
const { staged } = parseGitStatus(statusResult.stdout);
|
|
@@ -216,25 +258,37 @@ async function handleGitCommitWithAI(ctx: HandlerContext, ws: WSContext, msg: We
|
|
|
216
258
|
const recentCommits = logResult.stdout.trim() || 'No recent commits';
|
|
217
259
|
const stagedFiles = staged.map(f => `${f.status} ${f.path}`).join('\n');
|
|
218
260
|
const diff = truncateDiff(diffResult.stdout);
|
|
261
|
+
const diffBytes = diffResult.stdout.length;
|
|
219
262
|
|
|
220
263
|
const prompt = loadSkillPrompt('commit-message', { recentCommits, stagedFiles, diff }, workingDir)
|
|
221
264
|
?? `You are generating a git commit message for the following staged changes.\n\nRECENT COMMIT MESSAGES (for style reference):\n${recentCommits}\n\nSTAGED FILES:\n${stagedFiles}\n\nDIFF OF STAGED CHANGES:\n${diff}\n\nGenerate a commit message: imperative mood, max 72 characters, focus on "why". Respond with ONLY the commit message.`;
|
|
222
265
|
|
|
223
|
-
const
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
workingDir,
|
|
227
|
-
);
|
|
266
|
+
const systemPrompt = 'You are a commit message assistant. Respond with only the commit message, no preamble or explanation.';
|
|
267
|
+
|
|
268
|
+
const { result, attempts } = await runHaikuForCommitMessage(prompt, systemPrompt, workingDir, { tabId, startedAt, diffBytes });
|
|
228
269
|
|
|
229
|
-
if (
|
|
230
|
-
|
|
231
|
-
|
|
270
|
+
if (!haikuSucceeded(result)) {
|
|
271
|
+
const failure = classifyHaikuFailure(result);
|
|
272
|
+
logCommitMessageEvent(workingDir, {
|
|
273
|
+
tabId, attempt: attempts, success: false, reason: failure.reason,
|
|
274
|
+
exitCode: result.exitCode, timedOut: result.timedOut,
|
|
275
|
+
stderrTail: (result.stderr || '').trim().slice(-500),
|
|
276
|
+
latencyMs: Date.now() - startedAt, diffBytes,
|
|
277
|
+
});
|
|
278
|
+
console.error(`[git] commit-message failed (${failure.reason}):`, result.stderr || 'no stderr');
|
|
279
|
+
ctx.send(ws, { type: 'gitError', tabId, data: { error: failure.userMessage } });
|
|
232
280
|
return;
|
|
233
281
|
}
|
|
234
282
|
|
|
235
283
|
const commitMessage = extractCommitMessage(result.stdout.trim());
|
|
236
284
|
const autoCommit = !!msg.data?.autoCommit;
|
|
237
285
|
|
|
286
|
+
logCommitMessageEvent(workingDir, {
|
|
287
|
+
tabId, attempt: attempts, success: true,
|
|
288
|
+
latencyMs: Date.now() - startedAt, diffBytes, autoCommit,
|
|
289
|
+
messageLength: commitMessage.length,
|
|
290
|
+
});
|
|
291
|
+
|
|
238
292
|
ctx.send(ws, { type: 'gitCommitMessage', tabId, data: { message: commitMessage, autoCommit } });
|
|
239
293
|
|
|
240
294
|
if (autoCommit) {
|
|
@@ -250,6 +304,8 @@ async function handleGitCommitWithAI(ctx: HandlerContext, ws: WSContext, msg: We
|
|
|
250
304
|
}
|
|
251
305
|
} catch (error: unknown) {
|
|
252
306
|
sendGitError(ctx, ws, tabId, error);
|
|
307
|
+
} finally {
|
|
308
|
+
inflightCommitMessage.delete(tabId);
|
|
253
309
|
}
|
|
254
310
|
}
|
|
255
311
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// Copyright (c) 2025-present Mstro, Inc. All rights reserved.
|
|
2
|
-
// Licensed under the MIT License. See LICENSE file for details.
|
|
3
2
|
|
|
4
3
|
import { spawn } from 'node:child_process';
|
|
5
|
-
import {
|
|
4
|
+
import { randomUUID } from 'node:crypto';
|
|
5
|
+
import { appendFileSync, existsSync, mkdirSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
6
6
|
import { join } from 'node:path';
|
|
7
7
|
import type { HandlerContext } from './handler-context.js';
|
|
8
8
|
import type { GitFileStatus, WSContext } from './types.js';
|
|
@@ -171,17 +171,31 @@ export function truncateDiff(diff: string, maxLength = 8000): string {
|
|
|
171
171
|
return `${diff.slice(0, headSize)}\n\n... [diff truncated] ...\n\n${diff.slice(-tailSize)}`;
|
|
172
172
|
}
|
|
173
173
|
|
|
174
|
-
/**
|
|
174
|
+
/** Result of a Haiku spawn. `timedOut` is set when the process was killed by our timer. */
|
|
175
|
+
export interface HaikuResult {
|
|
176
|
+
stdout: string;
|
|
177
|
+
stderr: string;
|
|
178
|
+
exitCode: number;
|
|
179
|
+
timedOut: boolean;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** Spawn Claude Haiku with a prompt file and return captured output.
|
|
183
|
+
*
|
|
184
|
+
* Default timeout is 90s — measured p99 of Haiku responses is ~31s for tiny
|
|
185
|
+
* bouncer prompts; commit-message and PR-description prompts ship a full diff
|
|
186
|
+
* and run materially longer. 90s gives a 3x margin while still failing fast
|
|
187
|
+
* enough that the UI doesn't spin forever. */
|
|
175
188
|
export function spawnHaikuWithPrompt(
|
|
176
189
|
prompt: string,
|
|
177
190
|
systemPrompt: string,
|
|
178
191
|
workingDir: string,
|
|
179
|
-
timeoutMs =
|
|
180
|
-
): Promise<
|
|
192
|
+
timeoutMs = 90000,
|
|
193
|
+
): Promise<HaikuResult> {
|
|
181
194
|
return new Promise((resolve) => {
|
|
182
195
|
const tempDir = join(workingDir, '.mstro', 'tmp');
|
|
183
196
|
if (!existsSync(tempDir)) mkdirSync(tempDir, { recursive: true });
|
|
184
|
-
|
|
197
|
+
// randomUUID — Date.now() collided when two requests fired in the same ms.
|
|
198
|
+
const promptFile = join(tempDir, `haiku-${randomUUID()}.txt`);
|
|
185
199
|
writeFileSync(promptFile, prompt);
|
|
186
200
|
|
|
187
201
|
const args = ['--print', '--model', 'haiku', '--system-prompt', systemPrompt, promptFile];
|
|
@@ -189,25 +203,71 @@ export function spawnHaikuWithPrompt(
|
|
|
189
203
|
|
|
190
204
|
let stdout = '';
|
|
191
205
|
let stderr = '';
|
|
206
|
+
let timedOut = false;
|
|
192
207
|
proc.stdout?.on('data', (d: Buffer) => { stdout += d.toString(); });
|
|
193
208
|
proc.stderr?.on('data', (d: Buffer) => { stderr += d.toString(); });
|
|
194
209
|
|
|
195
|
-
const timer = setTimeout(() => proc.kill(), timeoutMs);
|
|
210
|
+
const timer = setTimeout(() => { timedOut = true; proc.kill(); }, timeoutMs);
|
|
196
211
|
|
|
197
212
|
proc.on('close', (code) => {
|
|
198
213
|
clearTimeout(timer);
|
|
199
214
|
try { unlinkSync(promptFile); } catch { /* ignore cleanup errors */ }
|
|
200
|
-
resolve({ stdout, stderr, exitCode: code ?? 1 });
|
|
215
|
+
resolve({ stdout, stderr, exitCode: code ?? 1, timedOut });
|
|
201
216
|
});
|
|
202
217
|
|
|
203
218
|
proc.on('error', (err: Error) => {
|
|
204
219
|
clearTimeout(timer);
|
|
205
220
|
try { unlinkSync(promptFile); } catch { /* ignore cleanup errors */ }
|
|
206
|
-
|
|
221
|
+
// ENOENT here means the `claude` binary wasn't found on PATH.
|
|
222
|
+
const enoent = (err as NodeJS.ErrnoException).code === 'ENOENT';
|
|
223
|
+
resolve({ stdout: '', stderr: enoent ? `claude: command not found (${err.message})` : err.message, exitCode: 1, timedOut: false });
|
|
207
224
|
});
|
|
208
225
|
});
|
|
209
226
|
}
|
|
210
227
|
|
|
228
|
+
/** Map a Haiku failure to an actionable user-facing message + a stable reason code. */
|
|
229
|
+
export function classifyHaikuFailure(result: HaikuResult): { reason: string; userMessage: string } {
|
|
230
|
+
const stderr = (result.stderr || '').toLowerCase();
|
|
231
|
+
const stdout = (result.stdout || '').toLowerCase();
|
|
232
|
+
const combined = `${stderr} ${stdout}`;
|
|
233
|
+
|
|
234
|
+
if (result.timedOut) {
|
|
235
|
+
return { reason: 'timeout', userMessage: 'Claude took too long to respond. Try again, or stage fewer files at once.' };
|
|
236
|
+
}
|
|
237
|
+
if (combined.includes('command not found') || combined.includes('enoent')) {
|
|
238
|
+
return { reason: 'binary_missing', userMessage: 'Claude CLI is not installed or not on PATH. Install it with `npm i -g @anthropic-ai/claude-code` and re-launch mstro.' };
|
|
239
|
+
}
|
|
240
|
+
if (/not\s+(logged|signed)\s*in|not\s+authenticated|please\s+(log|sign)\s*in|api\s*key|unauthor/.test(combined)) {
|
|
241
|
+
return { reason: 'auth', userMessage: 'Claude CLI is not authenticated. Run `claude /login` in a terminal, then try again.' };
|
|
242
|
+
}
|
|
243
|
+
if (/rate\s*limit|usage\s*limit|429|too\s+many\s+requests/.test(combined)) {
|
|
244
|
+
return { reason: 'rate_limit', userMessage: 'Claude rate limit reached. Wait a minute and try again.' };
|
|
245
|
+
}
|
|
246
|
+
if (/overloaded|529|service\s+unavailable|503/.test(combined)) {
|
|
247
|
+
return { reason: 'overloaded', userMessage: "Claude is temporarily overloaded. Try again in a few seconds." };
|
|
248
|
+
}
|
|
249
|
+
if (result.exitCode === 0 && !result.stdout.trim()) {
|
|
250
|
+
return { reason: 'empty_output', userMessage: 'Claude returned an empty response. Try again.' };
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const tail = (result.stderr || result.stdout || '').trim().slice(-200);
|
|
254
|
+
return { reason: 'unknown', userMessage: `Failed to generate commit message${tail ? `: ${tail}` : ''}` };
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/** Append a structured event to .mstro/logs/commit-message.jsonl for observability. */
|
|
258
|
+
export function logCommitMessageEvent(workingDir: string, entry: Record<string, unknown>): void {
|
|
259
|
+
try {
|
|
260
|
+
const logDir = join(workingDir, '.mstro', 'logs');
|
|
261
|
+
if (!existsSync(logDir)) mkdirSync(logDir, { recursive: true });
|
|
262
|
+
const logFile = join(logDir, 'commit-message.jsonl');
|
|
263
|
+
const line = `${JSON.stringify({ timestamp: new Date().toISOString(), ...entry })}\n`;
|
|
264
|
+
appendFileSync(logFile, line, 'utf-8');
|
|
265
|
+
} catch (err) {
|
|
266
|
+
// Logging must never break the user-facing flow.
|
|
267
|
+
console.error('[git] failed to write commit-message log:', err);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
211
271
|
/**
|
|
212
272
|
* Strip injected coauthor/attribution lines from a commit message.
|
|
213
273
|
*/
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
// Copyright (c) 2025-present Mstro, Inc. All rights reserved.
|
|
2
|
-
// Licensed under the MIT License. See LICENSE file for details.
|
|
3
2
|
|
|
4
3
|
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
5
4
|
import { dirname, join } from 'node:path';
|
|
@@ -7,8 +6,21 @@ import { resolvePmDir } from '../plan/parser.js';
|
|
|
7
6
|
import type { Workspace } from '../plan/types.js';
|
|
8
7
|
import { executeGitCommand, handleGitStatus, spawnWithOutput } from './git-handlers.js';
|
|
9
8
|
import { handleGitLog } from './git-log-handlers.js';
|
|
9
|
+
import { parseGitStatus } from './git-utils.js';
|
|
10
10
|
import type { HandlerContext } from './handler-context.js';
|
|
11
|
-
import type { WebSocketMessage, WorktreeInfo, WSContext } from './types.js';
|
|
11
|
+
import type { GitFileStatus, WebSocketMessage, WorktreeInfo, WSContext } from './types.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* A file in the merge target's working tree whose uncommitted state would be
|
|
15
|
+
* lost if the merge proceeds. Used to drive the merge dialog's blocker state
|
|
16
|
+
* before the merge attempt and to translate the post-flight git error if a
|
|
17
|
+
* race lets one slip through.
|
|
18
|
+
*/
|
|
19
|
+
interface MergeBlocker {
|
|
20
|
+
path: string;
|
|
21
|
+
status: GitFileStatus['status'];
|
|
22
|
+
staged: boolean;
|
|
23
|
+
}
|
|
12
24
|
|
|
13
25
|
function persistBoardWorktree(workingDir: string, boardId: string, worktreePath: string | null, branch: string | null): void {
|
|
14
26
|
const pmDir = resolvePmDir(workingDir);
|
|
@@ -44,6 +56,8 @@ export async function handleGitWorktreeMessage(ctx: HandlerContext, ws: WSContex
|
|
|
44
56
|
gitWorktreeMerge: () => handleGitWorktreeMerge(ctx, ws, msg, tabId, gitDir),
|
|
45
57
|
gitMergeAbort: () => handleGitMergeAbort(ctx, ws, tabId, gitDir),
|
|
46
58
|
gitMergeComplete: () => handleGitMergeComplete(ctx, ws, msg, tabId, gitDir),
|
|
59
|
+
gitMergeStashPop: () => handleGitMergeStashPop(ctx, ws, msg, tabId, gitDir),
|
|
60
|
+
gitMergeDiscardBlockers: () => handleGitMergeDiscardBlockers(ctx, ws, msg, tabId, gitDir),
|
|
47
61
|
};
|
|
48
62
|
await handlers[msg.type]?.();
|
|
49
63
|
}
|
|
@@ -339,6 +353,64 @@ async function handleGitWorktreeCreatePR(ctx: HandlerContext, ws: WSContext, msg
|
|
|
339
353
|
}
|
|
340
354
|
}
|
|
341
355
|
|
|
356
|
+
/**
|
|
357
|
+
* Files in the target worktree's working tree whose dirty state intersects
|
|
358
|
+
* the set of files the merge would touch. These are the rows that drive the
|
|
359
|
+
* merge dialog's "uncommitted changes on <target>" blocker UI — surfacing
|
|
360
|
+
* them preflight prevents the user from ever hitting git's raw "would be
|
|
361
|
+
* overwritten" error.
|
|
362
|
+
*
|
|
363
|
+
* Detection strategy:
|
|
364
|
+
* - `git status --porcelain=v1` on the target's worktree → all dirty paths
|
|
365
|
+
* - `git diff --name-only target..source` → all paths the merge would touch
|
|
366
|
+
* - intersection = blockers
|
|
367
|
+
*
|
|
368
|
+
* Untracked files are included via the porcelain output (they collide with
|
|
369
|
+
* incoming additions of the same path). Files that are dirty but outside the
|
|
370
|
+
* merge's diff are NOT blockers — git would carry them through cleanly.
|
|
371
|
+
*/
|
|
372
|
+
async function detectMergeBlockers(
|
|
373
|
+
targetWorktreePath: string,
|
|
374
|
+
sourceBranch: string,
|
|
375
|
+
targetBranch: string,
|
|
376
|
+
): Promise<MergeBlocker[]> {
|
|
377
|
+
const statusResult = await executeGitCommand(['status', '--porcelain=v1'], targetWorktreePath);
|
|
378
|
+
if (statusResult.exitCode !== 0) return [];
|
|
379
|
+
const { staged, unstaged, untracked } = parseGitStatus(statusResult.stdout);
|
|
380
|
+
|
|
381
|
+
const diffResult = await executeGitCommand(
|
|
382
|
+
['diff', '--name-only', `${targetBranch}..${sourceBranch}`],
|
|
383
|
+
targetWorktreePath,
|
|
384
|
+
);
|
|
385
|
+
if (diffResult.exitCode !== 0) return [];
|
|
386
|
+
const mergeTouches = new Set(
|
|
387
|
+
diffResult.stdout.split('\n').map(s => s.trim()).filter(Boolean),
|
|
388
|
+
);
|
|
389
|
+
if (mergeTouches.size === 0) return [];
|
|
390
|
+
|
|
391
|
+
// Prefer the worktree (unstaged) status when both index and worktree are
|
|
392
|
+
// dirty — that's what the user would lose. Fall back to staged, then
|
|
393
|
+
// untracked. Order in the result list groups deletions last so the UI can
|
|
394
|
+
// surface them where users expect.
|
|
395
|
+
const seen = new Map<string, MergeBlocker>();
|
|
396
|
+
const consider = (entries: GitFileStatus[]) => {
|
|
397
|
+
for (const entry of entries) {
|
|
398
|
+
if (!mergeTouches.has(entry.path)) continue;
|
|
399
|
+
if (seen.has(entry.path)) continue;
|
|
400
|
+
seen.set(entry.path, { path: entry.path, status: entry.status, staged: entry.staged });
|
|
401
|
+
}
|
|
402
|
+
};
|
|
403
|
+
consider(unstaged);
|
|
404
|
+
consider(staged);
|
|
405
|
+
consider(untracked);
|
|
406
|
+
|
|
407
|
+
return Array.from(seen.values()).sort((a, b) => {
|
|
408
|
+
if (a.status === 'D' && b.status !== 'D') return 1;
|
|
409
|
+
if (a.status !== 'D' && b.status === 'D') return -1;
|
|
410
|
+
return a.path.localeCompare(b.path);
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
|
|
342
414
|
async function handleGitMergePreview(ctx: HandlerContext, ws: WSContext, msg: WebSocketMessage, tabId: string, workingDir: string): Promise<void> {
|
|
343
415
|
try {
|
|
344
416
|
const { sourceBranch, targetBranch } = msg.data || {};
|
|
@@ -374,10 +446,30 @@ async function handleGitMergePreview(ctx: HandlerContext, ws: WSContext, msg: We
|
|
|
374
446
|
return { hash: hash.trim(), message: rest.join('|').trim() };
|
|
375
447
|
});
|
|
376
448
|
|
|
449
|
+
// The merge always lands on the main worktree currently checked out to
|
|
450
|
+
// the target branch. If main isn't on `targetBranch` we can't preflight
|
|
451
|
+
// blockers (the dirty state wouldn't be the relevant one), so skip the
|
|
452
|
+
// check and let the post-flight handler surface the branch-mismatch
|
|
453
|
+
// error like before.
|
|
454
|
+
const mainPath = await resolveMainWorktreePath(workingDir);
|
|
455
|
+
const mainBranchResult = await executeGitCommand(['rev-parse', '--abbrev-ref', 'HEAD'], mainPath);
|
|
456
|
+
const mainOnTarget = mainBranchResult.stdout.trim() === targetBranch;
|
|
457
|
+
const blockers = mainOnTarget
|
|
458
|
+
? await detectMergeBlockers(mainPath, sourceBranch, targetBranch)
|
|
459
|
+
: [];
|
|
460
|
+
|
|
377
461
|
ctx.send(ws, {
|
|
378
462
|
type: 'gitMergePreviewResult',
|
|
379
463
|
tabId,
|
|
380
|
-
data: {
|
|
464
|
+
data: {
|
|
465
|
+
clean,
|
|
466
|
+
conflicts,
|
|
467
|
+
stat,
|
|
468
|
+
commits,
|
|
469
|
+
ahead: commits.length,
|
|
470
|
+
targetWorktreePath: mainPath,
|
|
471
|
+
targetWorktreeBlockers: blockers,
|
|
472
|
+
},
|
|
381
473
|
});
|
|
382
474
|
} catch (error: unknown) {
|
|
383
475
|
ctx.send(ws, { type: 'gitError', tabId, data: { error: error instanceof Error ? error.message : String(error) } });
|
|
@@ -482,9 +574,103 @@ export function findWorktreePathForBranch(porcelainOutput: string, branchName: s
|
|
|
482
574
|
return null;
|
|
483
575
|
}
|
|
484
576
|
|
|
577
|
+
/**
|
|
578
|
+
* Parse a git stderr buffer for the "your local changes / untracked working
|
|
579
|
+
* tree files would be overwritten by merge" message. Returned paths feed the
|
|
580
|
+
* web client's blocker UI when a merge slips through preflight (race against
|
|
581
|
+
* a teammate's auto-formatter, stale status, etc.).
|
|
582
|
+
*/
|
|
583
|
+
function parseOverwritePaths(stderr: string): string[] {
|
|
584
|
+
const lines = stderr.split('\n');
|
|
585
|
+
const headerIdx = lines.findIndex(l =>
|
|
586
|
+
/your local changes to the following files would be overwritten/i.test(l) ||
|
|
587
|
+
/untracked working tree files would be overwritten/i.test(l),
|
|
588
|
+
);
|
|
589
|
+
if (headerIdx < 0) return [];
|
|
590
|
+
const paths: string[] = [];
|
|
591
|
+
for (let i = headerIdx + 1; i < lines.length; i++) {
|
|
592
|
+
const line = lines[i];
|
|
593
|
+
const trimmed = line.trim();
|
|
594
|
+
if (!trimmed) continue;
|
|
595
|
+
if (/^(merge with strategy|aborting|please commit)/i.test(trimmed)) break;
|
|
596
|
+
// Git indents each path with a tab; tolerate spaces too.
|
|
597
|
+
if (/^[\t ]/.test(line)) {
|
|
598
|
+
paths.push(trimmed);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
return paths;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
const STASH_MESSAGE_TAG = 'mstro:pre-merge';
|
|
605
|
+
|
|
606
|
+
async function pushMergeStash(targetWorktreePath: string, sourceBranch: string, targetBranch: string): Promise<{ ref: string; sha: string; message: string } | null> {
|
|
607
|
+
const message = `${STASH_MESSAGE_TAG} ${targetBranch} <- ${sourceBranch} @ ${new Date().toISOString()}`;
|
|
608
|
+
const result = await executeGitCommand(['stash', 'push', '-u', '-m', message], targetWorktreePath);
|
|
609
|
+
if (result.exitCode !== 0) return null;
|
|
610
|
+
// "No local changes to save" exits 0 but does not push a stash; detect by
|
|
611
|
+
// checking whether stash@{0}'s message matches what we just wrote.
|
|
612
|
+
const top = await executeGitCommand(['stash', 'list', '-n', '1', '--format=%H%x09%gs'], targetWorktreePath);
|
|
613
|
+
if (top.exitCode !== 0) return null;
|
|
614
|
+
const [sha, ...rest] = top.stdout.trim().split('\t');
|
|
615
|
+
const stashedMsg = rest.join('\t');
|
|
616
|
+
if (!sha || !stashedMsg.includes(STASH_MESSAGE_TAG)) return null;
|
|
617
|
+
return { ref: 'stash@{0}', sha, message };
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
async function popMergeStashBySha(targetWorktreePath: string, sha: string): Promise<{ exitCode: number; error?: string }> {
|
|
621
|
+
const list = await executeGitCommand(['stash', 'list', '--format=%gd%x09%H'], targetWorktreePath);
|
|
622
|
+
if (list.exitCode !== 0) return { exitCode: list.exitCode, error: list.stderr || 'Failed to read stash list' };
|
|
623
|
+
const ref = list.stdout.split('\n').map(l => l.split('\t')).find(([, h]) => h === sha)?.[0];
|
|
624
|
+
if (!ref) return { exitCode: 1, error: 'Stash no longer exists — it may have already been popped or dropped.' };
|
|
625
|
+
const pop = await executeGitCommand(['stash', 'pop', ref], targetWorktreePath);
|
|
626
|
+
return { exitCode: pop.exitCode, error: pop.exitCode !== 0 ? pop.stderr : undefined };
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
/**
|
|
630
|
+
* Decide what shape of merge-failure payload to send the client. Splits the
|
|
631
|
+
* three error modes — index conflict, target-worktree blocker, generic — out
|
|
632
|
+
* of the main handler so each is a self-contained branch.
|
|
633
|
+
*/
|
|
634
|
+
async function buildMergeFailurePayload(
|
|
635
|
+
mainPath: string,
|
|
636
|
+
sourceBranch: string,
|
|
637
|
+
targetBranch: string,
|
|
638
|
+
mergeError: string | undefined,
|
|
639
|
+
): Promise<Record<string, unknown>> {
|
|
640
|
+
const conflictFiles = await detectMergeConflicts(mainPath);
|
|
641
|
+
if (conflictFiles.length > 0) {
|
|
642
|
+
return { success: false, conflictFiles, targetWorktreePath: mainPath };
|
|
643
|
+
}
|
|
644
|
+
const overwritten = parseOverwritePaths(mergeError || '');
|
|
645
|
+
if (overwritten.length > 0) {
|
|
646
|
+
const blockers = await detectMergeBlockers(mainPath, sourceBranch, targetBranch);
|
|
647
|
+
const targetWorktreeBlockers: MergeBlocker[] = blockers.length > 0
|
|
648
|
+
? blockers
|
|
649
|
+
: overwritten.map(p => ({ path: p, status: 'M' as const, staged: false }));
|
|
650
|
+
return { success: false, targetWorktreePath: mainPath, targetWorktreeBlockers };
|
|
651
|
+
}
|
|
652
|
+
return { success: false, error: mergeError || 'Merge failed', targetWorktreePath: mainPath };
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
function buildMergeSuccessPayload(
|
|
656
|
+
mainPath: string,
|
|
657
|
+
mergeCommit: string,
|
|
658
|
+
warnings: string[],
|
|
659
|
+
stash: { ref: string; sha: string; message: string } | null,
|
|
660
|
+
): Record<string, unknown> {
|
|
661
|
+
const data: Record<string, unknown> = { success: true, mergeCommit, targetWorktreePath: mainPath };
|
|
662
|
+
if (warnings.length > 0) data.warnings = warnings;
|
|
663
|
+
if (stash) {
|
|
664
|
+
data.stashRef = stash.ref;
|
|
665
|
+
data.stashSha = stash.sha;
|
|
666
|
+
data.stashMessage = stash.message;
|
|
667
|
+
}
|
|
668
|
+
return data;
|
|
669
|
+
}
|
|
670
|
+
|
|
485
671
|
async function handleGitWorktreeMerge(ctx: HandlerContext, ws: WSContext, msg: WebSocketMessage, tabId: string, workingDir: string): Promise<void> {
|
|
486
672
|
try {
|
|
487
|
-
const { sourceBranch, targetBranch, strategy, commitMessage, deleteWorktree, deleteBranch } = msg.data || {};
|
|
673
|
+
const { sourceBranch, targetBranch, strategy, commitMessage, deleteWorktree, deleteBranch, stashFirst } = msg.data || {};
|
|
488
674
|
if (!sourceBranch || !targetBranch || !strategy) {
|
|
489
675
|
ctx.send(ws, { type: 'gitError', tabId, data: { error: 'Source branch, target branch, and strategy are required' } });
|
|
490
676
|
return;
|
|
@@ -494,40 +680,97 @@ async function handleGitWorktreeMerge(ctx: HandlerContext, ws: WSContext, msg: W
|
|
|
494
680
|
|
|
495
681
|
const mainBranchResult = await executeGitCommand(['rev-parse', '--abbrev-ref', 'HEAD'], mainPath);
|
|
496
682
|
if (mainBranchResult.stdout.trim() !== targetBranch) {
|
|
497
|
-
ctx.send(ws, { type: 'gitWorktreeMergeResult', tabId, data: { success: false, error: `Switch the main worktree to "${targetBranch}" before merging
|
|
683
|
+
ctx.send(ws, { type: 'gitWorktreeMergeResult', tabId, data: { success: false, error: `Switch the main worktree to "${targetBranch}" before merging`, targetWorktreePath: mainPath } });
|
|
498
684
|
return;
|
|
499
685
|
}
|
|
500
686
|
|
|
501
|
-
|
|
687
|
+
// Stash first if requested. A no-op result (nothing to stash, e.g. user
|
|
688
|
+
// already cleaned up between preflight and merge) is fine — we still
|
|
689
|
+
// proceed with the merge.
|
|
690
|
+
const stash = stashFirst ? await pushMergeStash(mainPath, sourceBranch, targetBranch) : null;
|
|
502
691
|
|
|
692
|
+
const headBefore = await executeGitCommand(['rev-parse', 'HEAD'], mainPath);
|
|
503
693
|
const mergeResult = await executeMergeStrategy(strategy, sourceBranch, commitMessage, mainPath);
|
|
694
|
+
|
|
504
695
|
if (mergeResult.exitCode !== 0) {
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
696
|
+
// Best-effort restore of the user's working tree before reporting.
|
|
697
|
+
// If pop conflicts, the stash stays around and the user can recover it
|
|
698
|
+
// manually — we still surface the original merge error.
|
|
699
|
+
if (stash) await popMergeStashBySha(mainPath, stash.sha).catch(() => undefined);
|
|
700
|
+
const data = await buildMergeFailurePayload(mainPath, sourceBranch, targetBranch, mergeResult.error);
|
|
509
701
|
ctx.send(ws, { type: 'gitWorktreeMergeResult', tabId, data });
|
|
510
702
|
return;
|
|
511
703
|
}
|
|
512
704
|
|
|
513
705
|
const headAfter = await executeGitCommand(['rev-parse', 'HEAD'], mainPath);
|
|
514
706
|
if (headBefore.stdout.trim() === headAfter.stdout.trim()) {
|
|
515
|
-
|
|
707
|
+
if (stash) await popMergeStashBySha(mainPath, stash.sha).catch(() => undefined);
|
|
708
|
+
ctx.send(ws, { type: 'gitWorktreeMergeResult', tabId, data: { success: false, error: `Already up to date — "${sourceBranch}" has no new commits to merge into "${targetBranch}"`, targetWorktreePath: mainPath } });
|
|
516
709
|
return;
|
|
517
710
|
}
|
|
518
711
|
|
|
519
712
|
const commitHashResult = await executeGitCommand(['rev-parse', '--short', 'HEAD'], mainPath);
|
|
520
713
|
const { warnings, removedWorktreePath } = await cleanupAfterMerge(mainPath, sourceBranch, strategy, !!deleteWorktree, !!deleteBranch);
|
|
714
|
+
if (removedWorktreePath) cleanupWorktreeReferences(ctx, workingDir, removedWorktreePath);
|
|
715
|
+
|
|
716
|
+
const data = buildMergeSuccessPayload(mainPath, commitHashResult.stdout.trim(), warnings, stash);
|
|
717
|
+
ctx.send(ws, { type: 'gitWorktreeMergeResult', tabId, data });
|
|
718
|
+
} catch (error: unknown) {
|
|
719
|
+
ctx.send(ws, { type: 'gitError', tabId, data: { error: error instanceof Error ? error.message : String(error) } });
|
|
720
|
+
}
|
|
721
|
+
}
|
|
521
722
|
|
|
522
|
-
|
|
523
|
-
|
|
723
|
+
async function handleGitMergeStashPop(ctx: HandlerContext, ws: WSContext, msg: WebSocketMessage, tabId: string, workingDir: string): Promise<void> {
|
|
724
|
+
try {
|
|
725
|
+
const { stashSha, targetWorktreePath } = msg.data || {};
|
|
726
|
+
if (!stashSha) {
|
|
727
|
+
ctx.send(ws, { type: 'gitMergeStashPopped', tabId, data: { success: false, error: 'Missing stash reference' } });
|
|
728
|
+
return;
|
|
729
|
+
}
|
|
730
|
+
const path = targetWorktreePath || (await resolveMainWorktreePath(workingDir));
|
|
731
|
+
const result = await popMergeStashBySha(path, stashSha);
|
|
732
|
+
if (result.exitCode !== 0) {
|
|
733
|
+
ctx.send(ws, { type: 'gitMergeStashPopped', tabId, data: { success: false, error: result.error || 'Failed to pop stash', targetWorktreePath: path } });
|
|
734
|
+
return;
|
|
524
735
|
}
|
|
736
|
+
ctx.send(ws, { type: 'gitMergeStashPopped', tabId, data: { success: true, targetWorktreePath: path } });
|
|
737
|
+
// Refresh status so the file explorer / git view reflects the restored changes.
|
|
738
|
+
handleGitStatus(ctx, ws, tabId, path);
|
|
739
|
+
} catch (error: unknown) {
|
|
740
|
+
ctx.send(ws, { type: 'gitError', tabId, data: { error: error instanceof Error ? error.message : String(error) } });
|
|
741
|
+
}
|
|
742
|
+
}
|
|
525
743
|
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
744
|
+
async function handleGitMergeDiscardBlockers(ctx: HandlerContext, ws: WSContext, msg: WebSocketMessage, tabId: string, workingDir: string): Promise<void> {
|
|
745
|
+
try {
|
|
746
|
+
const { paths, targetWorktreePath } = msg.data || {};
|
|
747
|
+
if (!Array.isArray(paths) || paths.length === 0) {
|
|
748
|
+
ctx.send(ws, { type: 'gitMergeBlockersDiscarded', tabId, data: { success: false, error: 'No paths to discard' } });
|
|
749
|
+
return;
|
|
529
750
|
}
|
|
530
|
-
|
|
751
|
+
const path = targetWorktreePath || (await resolveMainWorktreePath(workingDir));
|
|
752
|
+
|
|
753
|
+
// Restore tracked files (modifications, deletions, stages) from HEAD.
|
|
754
|
+
// `git checkout HEAD -- <paths>` overwrites both index and worktree, which
|
|
755
|
+
// is exactly the "discard" semantics the UI promised the user.
|
|
756
|
+
const checkoutResult = await executeGitCommand(['checkout', 'HEAD', '--', ...paths], path);
|
|
757
|
+
// Untracked files won't be touched by `checkout HEAD --` (git refuses with
|
|
758
|
+
// "did not match any file(s) known to git" when ALL paths are untracked,
|
|
759
|
+
// but mixed lists succeed for the tracked subset). Run `git clean -f` on
|
|
760
|
+
// the originally-supplied list to remove any untracked leftovers — git
|
|
761
|
+
// ignores anything that's already gone, so this is safe to run after.
|
|
762
|
+
const cleanResult = await executeGitCommand(['clean', '-f', '--', ...paths], path);
|
|
763
|
+
|
|
764
|
+
if (checkoutResult.exitCode !== 0 && cleanResult.exitCode !== 0) {
|
|
765
|
+
ctx.send(ws, {
|
|
766
|
+
type: 'gitMergeBlockersDiscarded',
|
|
767
|
+
tabId,
|
|
768
|
+
data: { success: false, error: checkoutResult.stderr || cleanResult.stderr || 'Failed to discard changes', targetWorktreePath: path },
|
|
769
|
+
});
|
|
770
|
+
return;
|
|
771
|
+
}
|
|
772
|
+
ctx.send(ws, { type: 'gitMergeBlockersDiscarded', tabId, data: { success: true, targetWorktreePath: path, paths } });
|
|
773
|
+
handleGitStatus(ctx, ws, tabId, path);
|
|
531
774
|
} catch (error: unknown) {
|
|
532
775
|
ctx.send(ws, { type: 'gitError', tabId, data: { error: error instanceof Error ? error.message : String(error) } });
|
|
533
776
|
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
// Copyright (c) 2025-present Mstro, Inc. All rights reserved.
|
|
2
|
-
// Licensed under the MIT License. See LICENSE file for details.
|
|
3
2
|
|
|
4
3
|
import type { ChildProcess } from 'node:child_process';
|
|
5
4
|
import type { ImprovisationSessionManager } from '../../cli/improvisation-session-manager.js';
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
// Copyright (c) 2025-present Mstro, Inc. All rights reserved.
|
|
2
|
-
// Licensed under the MIT License. See LICENSE file for details.
|
|
3
2
|
|
|
4
3
|
/**
|
|
5
4
|
* WebSocket Handler for Improvisation Sessions
|
|
@@ -235,7 +234,7 @@ export class WebSocketImproviseHandler implements HandlerContext {
|
|
|
235
234
|
case 'git': return handleGitMessage(this, ws, msg, tabId, workingDir);
|
|
236
235
|
case 'quality': return handleQualityMessage(this, ws, msg, tabId, workingDir, permission);
|
|
237
236
|
case 'plan': return handlePlanMessage(this, ws, msg, tabId, workingDir, permission);
|
|
238
|
-
case 'fileUpload': return this.handleFileUploadMessage(ws, msg, tabId,
|
|
237
|
+
case 'fileUpload': return this.handleFileUploadMessage(ws, msg, tabId, effectiveDir, permission);
|
|
239
238
|
case 'fileDownload': return this.handleFileDownloadMessage(ws, msg, tabId, effectiveDir);
|
|
240
239
|
}
|
|
241
240
|
}
|
|
@@ -259,14 +258,14 @@ export class WebSocketImproviseHandler implements HandlerContext {
|
|
|
259
258
|
|
|
260
259
|
private handleFileUploadMessage(ws: WSContext, msg: WebSocketMessage, tabId: string, workingDir: string, permission?: 'view'): void {
|
|
261
260
|
if (!this.fileUploadHandler) {
|
|
262
|
-
this.fileUploadHandler = new FileUploadHandler(
|
|
261
|
+
this.fileUploadHandler = new FileUploadHandler(this);
|
|
263
262
|
}
|
|
264
263
|
const handler = this.fileUploadHandler;
|
|
265
264
|
const send = this.send.bind(this);
|
|
266
265
|
|
|
267
266
|
switch (msg.type) {
|
|
268
267
|
case 'fileUploadStart':
|
|
269
|
-
handler.handleUploadStart(ws, send, tabId, msg.data, permission);
|
|
268
|
+
handler.handleUploadStart(ws, send, tabId, msg.data, workingDir, permission);
|
|
270
269
|
break;
|
|
271
270
|
case 'fileUploadChunk':
|
|
272
271
|
handler.handleUploadChunk(ws, send, tabId, msg.data);
|