mstro-app 0.5.0 → 0.5.5
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 +11 -11
- package/README.md +75 -28
- package/bin/commands/config.js +1 -2
- package/bin/mstro.js +55 -5
- package/bin/postinstall.js +0 -1
- package/dist/server/cli/eta-estimator.d.ts +55 -0
- package/dist/server/cli/eta-estimator.d.ts.map +1 -0
- package/dist/server/cli/eta-estimator.js +222 -0
- package/dist/server/cli/eta-estimator.js.map +1 -0
- 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 +50 -0
- package/dist/server/cli/headless/stall-assessor.d.ts.map +1 -1
- package/dist/server/cli/headless/stall-assessor.js +64 -10
- package/dist/server/cli/headless/stall-assessor.js.map +1 -1
- package/dist/server/cli/headless/tool-watchdog.d.ts +21 -0
- package/dist/server/cli/headless/tool-watchdog.d.ts.map +1 -1
- package/dist/server/cli/headless/tool-watchdog.js +19 -13
- 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 +5 -2
- 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 +5 -1
- package/dist/server/cli/improvisation-output-queue.d.ts.map +1 -1
- package/dist/server/cli/improvisation-output-queue.js +30 -8
- 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 +29 -0
- package/dist/server/cli/improvisation-session-manager.d.ts.map +1 -1
- package/dist/server/cli/improvisation-session-manager.js +50 -2
- package/dist/server/cli/improvisation-session-manager.js.map +1 -1
- package/dist/server/cli/improvisation-types.d.ts +2 -0
- 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/engines/EngineEvent.d.ts +126 -0
- package/dist/server/engines/EngineEvent.d.ts.map +1 -0
- package/dist/server/engines/EngineEvent.js +11 -0
- package/dist/server/engines/EngineEvent.js.map +1 -0
- package/dist/server/engines/claude/ClaudeCodeEngine.d.ts +47 -0
- package/dist/server/engines/claude/ClaudeCodeEngine.d.ts.map +1 -0
- package/dist/server/engines/claude/ClaudeCodeEngine.js +338 -0
- package/dist/server/engines/claude/ClaudeCodeEngine.js.map +1 -0
- package/dist/server/engines/factory.d.ts +21 -0
- package/dist/server/engines/factory.d.ts.map +1 -0
- package/dist/server/engines/factory.js +152 -0
- package/dist/server/engines/factory.js.map +1 -0
- package/dist/server/engines/opencode/OpenCodeEngine.d.ts +148 -0
- package/dist/server/engines/opencode/OpenCodeEngine.d.ts.map +1 -0
- package/dist/server/engines/opencode/OpenCodeEngine.js +630 -0
- package/dist/server/engines/opencode/OpenCodeEngine.js.map +1 -0
- package/dist/server/engines/opencode/OpenCodeServerManager.d.ts +172 -0
- package/dist/server/engines/opencode/OpenCodeServerManager.d.ts.map +1 -0
- package/dist/server/engines/opencode/OpenCodeServerManager.js +390 -0
- package/dist/server/engines/opencode/OpenCodeServerManager.js.map +1 -0
- package/dist/server/engines/opencode/model-catalog.d.ts +94 -0
- package/dist/server/engines/opencode/model-catalog.d.ts.map +1 -0
- package/dist/server/engines/opencode/model-catalog.js +141 -0
- package/dist/server/engines/opencode/model-catalog.js.map +1 -0
- package/dist/server/engines/types.d.ts +146 -0
- package/dist/server/engines/types.d.ts.map +1 -0
- package/dist/server/engines/types.js +4 -0
- package/dist/server/engines/types.js.map +1 -0
- package/dist/server/index.js +1 -2
- 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 +17 -4
- package/dist/server/mcp/bouncer-haiku.d.ts.map +1 -1
- package/dist/server/mcp/bouncer-haiku.js +8 -125
- package/dist/server/mcp/bouncer-haiku.js.map +1 -1
- package/dist/server/mcp/bouncer-integration.d.ts +45 -0
- package/dist/server/mcp/bouncer-integration.d.ts.map +1 -1
- package/dist/server/mcp/bouncer-integration.js +69 -6
- package/dist/server/mcp/bouncer-integration.js.map +1 -1
- package/dist/server/mcp/classifier/BouncerClassifier.d.ts +34 -0
- package/dist/server/mcp/classifier/BouncerClassifier.d.ts.map +1 -0
- package/dist/server/mcp/classifier/BouncerClassifier.js +4 -0
- package/dist/server/mcp/classifier/BouncerClassifier.js.map +1 -0
- package/dist/server/mcp/classifier/ClaudeBouncerClassifier.d.ts +17 -0
- package/dist/server/mcp/classifier/ClaudeBouncerClassifier.d.ts.map +1 -0
- package/dist/server/mcp/classifier/ClaudeBouncerClassifier.js +142 -0
- package/dist/server/mcp/classifier/ClaudeBouncerClassifier.js.map +1 -0
- package/dist/server/mcp/classifier/OpenCodeBouncerClassifier.d.ts +68 -0
- package/dist/server/mcp/classifier/OpenCodeBouncerClassifier.d.ts.map +1 -0
- package/dist/server/mcp/classifier/OpenCodeBouncerClassifier.js +182 -0
- package/dist/server/mcp/classifier/OpenCodeBouncerClassifier.js.map +1 -0
- package/dist/server/mcp/classifier/factory.d.ts +70 -0
- package/dist/server/mcp/classifier/factory.d.ts.map +1 -0
- package/dist/server/mcp/classifier/factory.js +155 -0
- package/dist/server/mcp/classifier/factory.js.map +1 -0
- 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/agent-resolver.d.ts +26 -0
- package/dist/server/services/plan/agent-resolver.d.ts.map +1 -0
- package/dist/server/services/plan/agent-resolver.js +102 -0
- package/dist/server/services/plan/agent-resolver.js.map +1 -0
- 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 +59 -12
- 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 +48 -4
- 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 +33 -2
- 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 +1 -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 +1 -0
- 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 +76 -2
- package/dist/server/services/settings.d.ts.map +1 -1
- package/dist/server/services/settings.js +127 -5
- 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 +19 -7
- 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 +17 -1
- package/dist/server/services/websocket/handler.d.ts.map +1 -1
- package/dist/server/services/websocket/handler.js +57 -6
- 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 +78 -27
- package/dist/server/services/websocket/quality-complexity.js.map +1 -1
- package/dist/server/services/websocket/quality-eta.d.ts +47 -0
- package/dist/server/services/websocket/quality-eta.d.ts.map +1 -0
- package/dist/server/services/websocket/quality-eta.js +110 -0
- package/dist/server/services/websocket/quality-eta.js.map +1 -0
- package/dist/server/services/websocket/quality-grading.d.ts +69 -0
- package/dist/server/services/websocket/quality-grading.d.ts.map +1 -0
- package/dist/server/services/websocket/quality-grading.js +650 -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 +145 -8
- 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-operations.d.ts +34 -0
- package/dist/server/services/websocket/quality-operations.d.ts.map +1 -0
- package/dist/server/services/websocket/quality-operations.js +47 -0
- package/dist/server/services/websocket/quality-operations.js.map +1 -0
- package/dist/server/services/websocket/quality-persistence.d.ts +23 -0
- package/dist/server/services/websocket/quality-persistence.d.ts.map +1 -1
- package/dist/server/services/websocket/quality-persistence.js +38 -12
- package/dist/server/services/websocket/quality-persistence.js.map +1 -1
- package/dist/server/services/websocket/quality-review-agent.d.ts +1 -1
- package/dist/server/services/websocket/quality-review-agent.d.ts.map +1 -1
- package/dist/server/services/websocket/quality-review-agent.js +105 -57
- package/dist/server/services/websocket/quality-review-agent.js.map +1 -1
- package/dist/server/services/websocket/quality-service.d.ts +12 -2
- package/dist/server/services/websocket/quality-service.d.ts.map +1 -1
- package/dist/server/services/websocket/quality-service.js +387 -72
- package/dist/server/services/websocket/quality-service.js.map +1 -1
- package/dist/server/services/websocket/quality-tools.d.ts +22 -1
- package/dist/server/services/websocket/quality-tools.d.ts.map +1 -1
- package/dist/server/services/websocket/quality-tools.js +55 -3
- package/dist/server/services/websocket/quality-tools.js.map +1 -1
- package/dist/server/services/websocket/quality-types.d.ts +52 -3
- package/dist/server/services/websocket/quality-types.d.ts.map +1 -1
- package/dist/server/services/websocket/quality-types.js +1 -2
- package/dist/server/services/websocket/quality-types.js.map +1 -1
- package/dist/server/services/websocket/session-handlers.d.ts +3 -1
- package/dist/server/services/websocket/session-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/session-handlers.js +57 -10
- 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 +3 -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 +158 -43
- package/dist/server/services/websocket/session-initialization.js.map +1 -1
- package/dist/server/services/websocket/session-registry.d.ts +25 -0
- package/dist/server/services/websocket/session-registry.d.ts.map +1 -1
- package/dist/server/services/websocket/session-registry.js +19 -1
- package/dist/server/services/websocket/session-registry.js.map +1 -1
- package/dist/server/services/websocket/settings-handlers.d.ts +1 -1
- package/dist/server/services/websocket/settings-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/settings-handlers.js +35 -5
- 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 +7 -2
- package/dist/server/services/websocket/tab-broadcast.d.ts.map +1 -1
- package/dist/server/services/websocket/tab-broadcast.js +10 -3
- package/dist/server/services/websocket/tab-broadcast.js.map +1 -1
- package/dist/server/services/websocket/tab-event-buffer.d.ts +97 -8
- package/dist/server/services/websocket/tab-event-buffer.d.ts.map +1 -1
- package/dist/server/services/websocket/tab-event-buffer.js +138 -13
- package/dist/server/services/websocket/tab-event-buffer.js.map +1 -1
- package/dist/server/services/websocket/tab-event-replay.d.ts +29 -13
- package/dist/server/services/websocket/tab-event-replay.d.ts.map +1 -1
- package/dist/server/services/websocket/tab-event-replay.js +55 -3
- package/dist/server/services/websocket/tab-event-replay.js.map +1 -1
- package/dist/server/services/websocket/tab-handlers.d.ts +9 -1
- package/dist/server/services/websocket/tab-handlers.d.ts.map +1 -1
- package/dist/server/services/websocket/tab-handlers.js +47 -3
- 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 +30 -7
- package/dist/server/services/websocket/types.d.ts.map +1 -1
- package/dist/server/services/websocket/types.js +12 -7
- 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 +6 -4
- package/server/cli/eta-estimator.ts +249 -0
- 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 +93 -1
- package/server/cli/headless/tool-watchdog.ts +21 -1
- package/server/cli/headless/types.ts +0 -1
- package/server/cli/improvisation-attachments.ts +0 -1
- package/server/cli/improvisation-history-store.ts +4 -2
- package/server/cli/improvisation-movements.ts +0 -1
- package/server/cli/improvisation-output-queue.ts +29 -8
- package/server/cli/improvisation-retry.ts +0 -1
- package/server/cli/improvisation-session-manager.ts +54 -2
- package/server/cli/improvisation-types.ts +2 -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/engines/EngineEvent.ts +156 -0
- package/server/engines/claude/ClaudeCodeEngine.ts +404 -0
- package/server/engines/factory.ts +176 -0
- package/server/engines/opencode/OpenCodeEngine.ts +786 -0
- package/server/engines/opencode/OpenCodeServerManager.ts +577 -0
- package/server/engines/opencode/model-catalog.ts +217 -0
- package/server/engines/types.ts +173 -0
- package/server/index.ts +1 -2
- package/server/mcp/bouncer-cli.ts +0 -1
- package/server/mcp/bouncer-haiku.ts +21 -146
- package/server/mcp/bouncer-integration.ts +107 -6
- package/server/mcp/classifier/BouncerClassifier.ts +40 -0
- package/server/mcp/classifier/ClaudeBouncerClassifier.ts +189 -0
- package/server/mcp/classifier/OpenCodeBouncerClassifier.ts +305 -0
- package/server/mcp/classifier/factory.ts +195 -0
- 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/agent-resolver.ts +115 -0
- package/server/services/plan/agents/code-review.md +43 -11
- package/server/services/plan/board-config.ts +0 -1
- package/server/services/plan/composer.ts +63 -12
- package/server/services/plan/config-installer.ts +0 -1
- package/server/services/plan/dependency-resolver.ts +0 -1
- package/server/services/plan/executor.ts +48 -4
- 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 +39 -2
- 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 +1 -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 +4 -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 +161 -5
- 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 +20 -7
- 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 +62 -6
- 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 +80 -27
- package/server/services/websocket/quality-eta.ts +155 -0
- package/server/services/websocket/quality-grading.ts +834 -0
- package/server/services/websocket/quality-handlers.ts +153 -8
- package/server/services/websocket/quality-linting.ts +0 -1
- package/server/services/websocket/quality-operations.ts +72 -0
- package/server/services/websocket/quality-persistence.ts +47 -8
- package/server/services/websocket/quality-review-agent.ts +154 -65
- package/server/services/websocket/quality-service.ts +415 -68
- package/server/services/websocket/quality-tools.ts +62 -3
- package/server/services/websocket/quality-types.ts +61 -4
- package/server/services/websocket/session-handlers.ts +64 -11
- package/server/services/websocket/session-history.ts +3 -1
- package/server/services/websocket/session-initialization.ts +189 -47
- package/server/services/websocket/session-registry.ts +37 -1
- package/server/services/websocket/settings-handlers.ts +41 -5
- 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 +10 -3
- package/server/services/websocket/tab-event-buffer.ts +143 -12
- package/server/services/websocket/tab-event-replay.ts +70 -4
- package/server/services/websocket/tab-handlers.ts +53 -6
- package/server/services/websocket/terminal-handlers.ts +39 -3
- package/server/services/websocket/types.ts +39 -8
- 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
|
@@ -0,0 +1,650 @@
|
|
|
1
|
+
// Copyright (c) 2025-present Mstro, Inc. All rights reserved.
|
|
2
|
+
// ============================================================================
|
|
3
|
+
// Category -> Dimension Mapping
|
|
4
|
+
// ============================================================================
|
|
5
|
+
const SECURITY_CATEGORIES = new Set(['security']);
|
|
6
|
+
const RELIABILITY_CATEGORIES = new Set(['bugs', 'logic', 'performance', 'complexity', 'build']);
|
|
7
|
+
const MAINTAINABILITY_CATEGORIES = new Set([
|
|
8
|
+
'lint',
|
|
9
|
+
'linting',
|
|
10
|
+
'format',
|
|
11
|
+
'file-length',
|
|
12
|
+
'function-length',
|
|
13
|
+
'architecture',
|
|
14
|
+
'oop',
|
|
15
|
+
'maintainability',
|
|
16
|
+
]);
|
|
17
|
+
/**
|
|
18
|
+
* Map a finding category to one of the three quality dimensions.
|
|
19
|
+
* Unknown categories default to maintainability (the catch-all bucket) so
|
|
20
|
+
* that surprise categories never silently disappear from the grade.
|
|
21
|
+
*/
|
|
22
|
+
export function categoryToDimension(category) {
|
|
23
|
+
if (SECURITY_CATEGORIES.has(category))
|
|
24
|
+
return 'security';
|
|
25
|
+
if (RELIABILITY_CATEGORIES.has(category))
|
|
26
|
+
return 'reliability';
|
|
27
|
+
if (MAINTAINABILITY_CATEGORIES.has(category))
|
|
28
|
+
return 'maintainability';
|
|
29
|
+
return 'maintainability';
|
|
30
|
+
}
|
|
31
|
+
/** Categories that represent architectural problems — used by the arch penalty. */
|
|
32
|
+
const ARCHITECTURE_CATEGORIES = new Set(['architecture', 'oop']);
|
|
33
|
+
// ============================================================================
|
|
34
|
+
// Score Bands & Modifier Math
|
|
35
|
+
// ============================================================================
|
|
36
|
+
/**
|
|
37
|
+
* Score boundaries for each base grade. Note the gap between C (70+) and F+
|
|
38
|
+
* (≤69): the band 60-69 maps to F+ instead of D, per product spec ("60s and
|
|
39
|
+
* below is F").
|
|
40
|
+
*/
|
|
41
|
+
const BASE_BAND_TOP = {
|
|
42
|
+
A: 100,
|
|
43
|
+
B: 89,
|
|
44
|
+
C: 79,
|
|
45
|
+
F: 69, // F covers 56-69 (F+ for 65-69, F for 56-64) — F- splits off below
|
|
46
|
+
};
|
|
47
|
+
const BASE_BAND_BOTTOM = {
|
|
48
|
+
A: 90,
|
|
49
|
+
B: 80,
|
|
50
|
+
C: 70,
|
|
51
|
+
F: 56, // F- covers 0-55 — handled specially in scoreToGrade()
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* Convert a 0-100 score to the full letter grade including +/- modifier.
|
|
55
|
+
*
|
|
56
|
+
* Within an A/B/C band, the band is split into thirds:
|
|
57
|
+
* X- bottom third (e.g., A-: 90-92)
|
|
58
|
+
* X middle third (e.g., A : 93-96)
|
|
59
|
+
* X+ top third (e.g., A+: 97-100)
|
|
60
|
+
*
|
|
61
|
+
* The F band uses two slices instead of three because there is no academic
|
|
62
|
+
* "F0" anchor and the user wanted F+/F/F-:
|
|
63
|
+
* F- 0-55 "critically broken"
|
|
64
|
+
* F 56-64 "broken"
|
|
65
|
+
* F+ 65-69 "barely failing"
|
|
66
|
+
*
|
|
67
|
+
* Compile/critical-severity hard caps are applied separately, not by score.
|
|
68
|
+
*/
|
|
69
|
+
export function scoreToGrade(score) {
|
|
70
|
+
if (score >= 97)
|
|
71
|
+
return 'A+';
|
|
72
|
+
if (score >= 93)
|
|
73
|
+
return 'A';
|
|
74
|
+
if (score >= 90)
|
|
75
|
+
return 'A-';
|
|
76
|
+
if (score >= 87)
|
|
77
|
+
return 'B+';
|
|
78
|
+
if (score >= 83)
|
|
79
|
+
return 'B';
|
|
80
|
+
if (score >= 80)
|
|
81
|
+
return 'B-';
|
|
82
|
+
if (score >= 77)
|
|
83
|
+
return 'C+';
|
|
84
|
+
if (score >= 73)
|
|
85
|
+
return 'C';
|
|
86
|
+
if (score >= 70)
|
|
87
|
+
return 'C-';
|
|
88
|
+
if (score >= 65)
|
|
89
|
+
return 'F+';
|
|
90
|
+
if (score >= 56)
|
|
91
|
+
return 'F';
|
|
92
|
+
return 'F-';
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Legacy single-letter conversion. Returns the *base* grade only (no
|
|
96
|
+
* modifier) for compatibility with callers that pre-date the +/- rollout
|
|
97
|
+
* (`scoreBreakdown.categoryPenalties[].grade`, etc.). New surfaces should
|
|
98
|
+
* call `scoreToGrade()` instead.
|
|
99
|
+
*/
|
|
100
|
+
export function gradeFromScore(score) {
|
|
101
|
+
const full = scoreToGrade(score);
|
|
102
|
+
// Strip the modifier so legacy callers still see exactly one of A/B/C/F.
|
|
103
|
+
return baseGradeOf(full);
|
|
104
|
+
}
|
|
105
|
+
/** Strip the +/- modifier from a letter grade. */
|
|
106
|
+
function baseGradeOf(g) {
|
|
107
|
+
if (g === 'N/A' || g === 'D')
|
|
108
|
+
return g;
|
|
109
|
+
if (g.startsWith('A'))
|
|
110
|
+
return 'A';
|
|
111
|
+
if (g.startsWith('B'))
|
|
112
|
+
return 'B';
|
|
113
|
+
if (g.startsWith('C'))
|
|
114
|
+
return 'C';
|
|
115
|
+
return 'F';
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Linearly interpolate a score within a base band.
|
|
119
|
+
*
|
|
120
|
+
* `position` is in [0, 1]: 0 = "as bad as this grade gets" (band bottom),
|
|
121
|
+
* 1 = "as good as this grade gets" (band top, just below the next grade).
|
|
122
|
+
*/
|
|
123
|
+
function scoreInBand(grade, position) {
|
|
124
|
+
const clamped = Math.max(0, Math.min(1, position));
|
|
125
|
+
const bottom = BASE_BAND_BOTTOM[grade];
|
|
126
|
+
const top = BASE_BAND_TOP[grade];
|
|
127
|
+
return Math.round(bottom + (top - bottom) * clamped);
|
|
128
|
+
}
|
|
129
|
+
// ============================================================================
|
|
130
|
+
// Severity Helpers
|
|
131
|
+
// ============================================================================
|
|
132
|
+
function isSeverity(s) {
|
|
133
|
+
return s === 'critical' || s === 'high' || s === 'medium' || s === 'low';
|
|
134
|
+
}
|
|
135
|
+
function countSeverities(findings) {
|
|
136
|
+
const counts = { critical: 0, high: 0, medium: 0, low: 0, total: 0 };
|
|
137
|
+
for (const f of findings) {
|
|
138
|
+
if (!isSeverity(f.severity))
|
|
139
|
+
continue;
|
|
140
|
+
counts[f.severity]++;
|
|
141
|
+
counts.total++;
|
|
142
|
+
}
|
|
143
|
+
return counts;
|
|
144
|
+
}
|
|
145
|
+
function worstSeverity(counts) {
|
|
146
|
+
if (counts.critical > 0)
|
|
147
|
+
return 'critical';
|
|
148
|
+
if (counts.high > 0)
|
|
149
|
+
return 'high';
|
|
150
|
+
if (counts.medium > 0)
|
|
151
|
+
return 'medium';
|
|
152
|
+
if (counts.low > 0)
|
|
153
|
+
return 'low';
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
// ============================================================================
|
|
157
|
+
// Security Dimension
|
|
158
|
+
// ============================================================================
|
|
159
|
+
/**
|
|
160
|
+
* Security grading — strictest of the three dimensions. Any medium-or-worse
|
|
161
|
+
* security finding immediately drops the grade below B because security
|
|
162
|
+
* issues can't be amortized over codebase size.
|
|
163
|
+
*
|
|
164
|
+
* A critical security issue caps at F- (the worst grade). One low-severity
|
|
165
|
+
* finding still earns a B- because every team has a few.
|
|
166
|
+
*/
|
|
167
|
+
function gradeSecurity(findings) {
|
|
168
|
+
const counts = countSeverities(findings);
|
|
169
|
+
const worst = worstSeverity(counts);
|
|
170
|
+
if (counts.total === 0) {
|
|
171
|
+
return makeDimension('security', 100, '0 security findings', 0, null);
|
|
172
|
+
}
|
|
173
|
+
if (counts.critical > 0) {
|
|
174
|
+
// Critical security issue → F-, not just F. There's no recovering by
|
|
175
|
+
// averaging this away across a clean codebase.
|
|
176
|
+
return makeDimension('security', Math.max(0, 55 - counts.critical * 5), `${counts.critical} critical-severity security ${pluralize('issue', counts.critical)}`, counts.total, worst);
|
|
177
|
+
}
|
|
178
|
+
let baseGrade;
|
|
179
|
+
let position;
|
|
180
|
+
let rationale;
|
|
181
|
+
if (counts.high > 0) {
|
|
182
|
+
baseGrade = 'F';
|
|
183
|
+
position = 1 / (1 + counts.high);
|
|
184
|
+
rationale = `${counts.high} high-severity security ${pluralize('issue', counts.high)}`;
|
|
185
|
+
}
|
|
186
|
+
else if (counts.medium > 0) {
|
|
187
|
+
baseGrade = 'C';
|
|
188
|
+
position = 1 / (1 + counts.medium);
|
|
189
|
+
rationale = `${counts.medium} medium-severity security ${pluralize('issue', counts.medium)}`;
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
// Only low-severity findings.
|
|
193
|
+
baseGrade = 'B';
|
|
194
|
+
position = 1 / Math.max(1, counts.low);
|
|
195
|
+
rationale = `${counts.low} low-severity security ${pluralize('issue', counts.low)}`;
|
|
196
|
+
}
|
|
197
|
+
const score = scoreInBand(baseGrade, position);
|
|
198
|
+
return makeDimension('security', score, rationale, counts.total, worst);
|
|
199
|
+
}
|
|
200
|
+
function reliabilityByCount(n) {
|
|
201
|
+
// Stricter than Maintainability's count ladder: a couple of real bugs hurt
|
|
202
|
+
// more than a couple of lint warnings, but a single isolated medium bug on
|
|
203
|
+
// a small project shouldn't pin the codebase at C.
|
|
204
|
+
const label = `${n} reliability ${pluralize('issue', n)}`;
|
|
205
|
+
if (n <= 2)
|
|
206
|
+
return { grade: 'A', position: 1 - n / 2, label };
|
|
207
|
+
if (n <= 6)
|
|
208
|
+
return { grade: 'B', position: 1 - (n - 2) / 4, label };
|
|
209
|
+
if (n <= 15)
|
|
210
|
+
return { grade: 'C', position: 1 - (n - 6) / 9, label };
|
|
211
|
+
return { grade: 'F', position: 1 / (1 + (n - 15) / 15), label };
|
|
212
|
+
}
|
|
213
|
+
function reliabilityByDensity(n, kloc) {
|
|
214
|
+
// Density thresholds are tighter than Maintainability (5/10/25). A 50 KLOC
|
|
215
|
+
// codebase with 100 reliability bugs (density 2) is "minor cleanup", not
|
|
216
|
+
// pristine — but 1.4/KLOC is still A-band because real-world projects
|
|
217
|
+
// never get to zero. The escape hatch handles severity outliers above this.
|
|
218
|
+
const density = n / kloc;
|
|
219
|
+
const label = `${roundOne(density)} reliability ${pluralize('issue', n)} / KLOC`;
|
|
220
|
+
if (density < 1.5)
|
|
221
|
+
return { grade: 'A', position: 1 - density / 1.5, label };
|
|
222
|
+
if (density < 4)
|
|
223
|
+
return { grade: 'B', position: 1 - (density - 1.5) / 2.5, label };
|
|
224
|
+
if (density < 8)
|
|
225
|
+
return { grade: 'C', position: 1 - (density - 4) / 4, label };
|
|
226
|
+
return { grade: 'F', position: 1 / (1 + (density - 8) / 8), label };
|
|
227
|
+
}
|
|
228
|
+
function reliabilityEscape(counts) {
|
|
229
|
+
if (counts.critical > 0) {
|
|
230
|
+
return { grade: 'F', note: `${counts.critical} critical-severity ${pluralize('bug', counts.critical)}` };
|
|
231
|
+
}
|
|
232
|
+
if (counts.high > 0) {
|
|
233
|
+
return { grade: 'C', note: `${counts.high} high-severity ${pluralize('bug', counts.high)}` };
|
|
234
|
+
}
|
|
235
|
+
return null;
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Reliability grading — density-based with a severity escape hatch.
|
|
239
|
+
*
|
|
240
|
+
* - Empty / ≤1 low: A-band (clean by convention).
|
|
241
|
+
* - Density-based grade (≥5 KLOC) or count-based grade (<5 KLOC) drives
|
|
242
|
+
* the baseline. Both ladders mirror Maintainability's so reliability and
|
|
243
|
+
* maintainability remain comparable at a glance.
|
|
244
|
+
* - Severity escape: critical → F, high → C. This matches Maintainability and
|
|
245
|
+
* prevents a handful of medium-density bugs from being silently rated A
|
|
246
|
+
* when at least one is severe.
|
|
247
|
+
*
|
|
248
|
+
* Build/compile errors flow in via `build` category with severity `critical`
|
|
249
|
+
* and therefore land at F via the escape hatch — no special-case branching.
|
|
250
|
+
*/
|
|
251
|
+
function gradeReliability(findings, totalLines) {
|
|
252
|
+
const counts = countSeverities(findings);
|
|
253
|
+
const worst = worstSeverity(counts);
|
|
254
|
+
const kloc = Math.max(totalLines / 1000, 1.0);
|
|
255
|
+
if (counts.total === 0) {
|
|
256
|
+
return makeDimension('reliability', 100, '0 reliability findings', 0, null);
|
|
257
|
+
}
|
|
258
|
+
// ≤1 low and nothing else is treated as clean — every team has one.
|
|
259
|
+
if (counts.low <= 1 && counts.medium === 0 && counts.high === 0 && counts.critical === 0) {
|
|
260
|
+
return makeDimension('reliability', scoreInBand('A', 0.5), '1 low-severity reliability issue', counts.total, worst);
|
|
261
|
+
}
|
|
262
|
+
const band = kloc < 5 ? reliabilityByCount(counts.total) : reliabilityByDensity(counts.total, kloc);
|
|
263
|
+
const severityCap = reliabilityEscape(counts);
|
|
264
|
+
const useCap = severityCap && baseIsWorse(severityCap.grade, band.grade);
|
|
265
|
+
const finalGrade = useCap ? severityCap.grade : band.grade;
|
|
266
|
+
const finalPosition = useCap ? 0.5 : band.position;
|
|
267
|
+
const rationale = useCap ? `${band.label}, ${severityCap.note}` : band.label;
|
|
268
|
+
return makeDimension('reliability', scoreInBand(finalGrade, finalPosition), rationale, counts.total, worst);
|
|
269
|
+
}
|
|
270
|
+
function maintainabilityByCount(n) {
|
|
271
|
+
const label = `${n} maintainability ${pluralize('issue', n)}`;
|
|
272
|
+
if (n <= 5)
|
|
273
|
+
return { grade: 'A', position: 1 - n / 5, label };
|
|
274
|
+
if (n <= 15)
|
|
275
|
+
return { grade: 'B', position: 1 - (n - 5) / 10, label };
|
|
276
|
+
if (n <= 30)
|
|
277
|
+
return { grade: 'C', position: 1 - (n - 15) / 15, label };
|
|
278
|
+
return { grade: 'F', position: 1 / (1 + (n - 30) / 30), label };
|
|
279
|
+
}
|
|
280
|
+
function maintainabilityByDensity(n, kloc) {
|
|
281
|
+
const density = n / kloc;
|
|
282
|
+
const label = `${roundOne(density)} ${pluralize('issue', n)} / KLOC`;
|
|
283
|
+
if (density < 5)
|
|
284
|
+
return { grade: 'A', position: 1 - density / 5, label };
|
|
285
|
+
if (density < 10)
|
|
286
|
+
return { grade: 'B', position: 1 - (density - 5) / 5, label };
|
|
287
|
+
if (density < 25)
|
|
288
|
+
return { grade: 'C', position: 1 - (density - 10) / 15, label };
|
|
289
|
+
return { grade: 'F', position: 1 / (1 + (density - 25) / 25), label };
|
|
290
|
+
}
|
|
291
|
+
function maintainabilityEscape(counts) {
|
|
292
|
+
if (counts.critical > 0) {
|
|
293
|
+
return { grade: 'F', note: `${counts.critical} critical-severity ${pluralize('issue', counts.critical)}` };
|
|
294
|
+
}
|
|
295
|
+
if (counts.high > 0) {
|
|
296
|
+
return { grade: 'C', note: `${counts.high} high-severity ${pluralize('issue', counts.high)}` };
|
|
297
|
+
}
|
|
298
|
+
return null;
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Maintainability uses a density-based grade (issues per KLOC) once the
|
|
302
|
+
* codebase is at least 5 KLOC. For smaller codebases, density is too noisy
|
|
303
|
+
* (one extra lint issue moves density by 1.0+), so we fall back to absolute
|
|
304
|
+
* counts — preventing tiny projects from being unfairly penalized.
|
|
305
|
+
*
|
|
306
|
+
* Severity escape hatch: a critical maintainability finding (e.g., a 3000-
|
|
307
|
+
* line file with high cohesion-violation severity) caps at F; a high-severity
|
|
308
|
+
* one caps at C. "Worst wins" — we take min of density-grade and severity-cap.
|
|
309
|
+
*/
|
|
310
|
+
function gradeMaintainability(findings, totalLines) {
|
|
311
|
+
const counts = countSeverities(findings);
|
|
312
|
+
const kloc = Math.max(totalLines / 1000, 1.0);
|
|
313
|
+
if (counts.total === 0) {
|
|
314
|
+
return makeDimension('maintainability', 100, '0 maintainability findings', 0, null);
|
|
315
|
+
}
|
|
316
|
+
const band = kloc < 5 ? maintainabilityByCount(counts.total) : maintainabilityByDensity(counts.total, kloc);
|
|
317
|
+
const severityCap = maintainabilityEscape(counts);
|
|
318
|
+
const useCap = severityCap && baseIsWorse(severityCap.grade, band.grade);
|
|
319
|
+
const finalGrade = useCap ? severityCap.grade : band.grade;
|
|
320
|
+
const finalPosition = useCap ? 0.5 : band.position;
|
|
321
|
+
const rationale = useCap ? `${band.label}, ${severityCap.note}` : band.label;
|
|
322
|
+
return makeDimension('maintainability', scoreInBand(finalGrade, finalPosition), rationale, counts.total, worstSeverity(counts));
|
|
323
|
+
}
|
|
324
|
+
// ============================================================================
|
|
325
|
+
// Architectural Penalty
|
|
326
|
+
// ============================================================================
|
|
327
|
+
/**
|
|
328
|
+
* Drop a dimension's grade by N letters because of architectural findings.
|
|
329
|
+
*
|
|
330
|
+
* Rationale: a high-severity architectural problem (god class, leaky
|
|
331
|
+
* abstraction, broken layering) is qualitatively different from a long-file
|
|
332
|
+
* lint warning — it pollutes every change that touches the affected code.
|
|
333
|
+
* The user spec calls for explicit letter-grade drops:
|
|
334
|
+
*
|
|
335
|
+
* - 1 high-severity arch issue → drop 1 letter
|
|
336
|
+
* - 2+ high-severity arch issues → drop 2 letters
|
|
337
|
+
* - any critical-severity arch issue → drop 2 letters
|
|
338
|
+
*
|
|
339
|
+
* Letters drop A → B → C → F → F-. We never go lower than F-. The drop is
|
|
340
|
+
* applied AFTER the dimension's normal grading so the displayed score still
|
|
341
|
+
* reflects the underlying finding count, but the letter grade carries the
|
|
342
|
+
* architectural weight that a density-based score would otherwise miss.
|
|
343
|
+
*/
|
|
344
|
+
function archDropCount(archFindings) {
|
|
345
|
+
let highCount = 0;
|
|
346
|
+
let criticalCount = 0;
|
|
347
|
+
for (const f of archFindings) {
|
|
348
|
+
if (f.severity === 'critical')
|
|
349
|
+
criticalCount++;
|
|
350
|
+
else if (f.severity === 'high')
|
|
351
|
+
highCount++;
|
|
352
|
+
}
|
|
353
|
+
if (criticalCount >= 1)
|
|
354
|
+
return 2;
|
|
355
|
+
if (highCount >= 2)
|
|
356
|
+
return 2;
|
|
357
|
+
if (highCount >= 1)
|
|
358
|
+
return 1;
|
|
359
|
+
return 0;
|
|
360
|
+
}
|
|
361
|
+
const BASE_LETTERS = ['A', 'B', 'C', 'F'];
|
|
362
|
+
function gradeModifier(grade) {
|
|
363
|
+
if (grade.endsWith('+'))
|
|
364
|
+
return '+';
|
|
365
|
+
if (grade.endsWith('-'))
|
|
366
|
+
return '-';
|
|
367
|
+
return '';
|
|
368
|
+
}
|
|
369
|
+
function applyModifierToTargetBase(targetBase, modifier) {
|
|
370
|
+
// F's modifier semantics differ from A/B/C: F+ is "barely failing" while
|
|
371
|
+
// A+/B+/C+ are "top of band." For simplicity we map any modifier on F to
|
|
372
|
+
// its matching variant, and use F- (the worst) for any post-F overshoot.
|
|
373
|
+
if (targetBase === 'F') {
|
|
374
|
+
if (modifier === '+')
|
|
375
|
+
return 'F+';
|
|
376
|
+
if (modifier === '-')
|
|
377
|
+
return 'F-';
|
|
378
|
+
return 'F';
|
|
379
|
+
}
|
|
380
|
+
if (modifier === '+')
|
|
381
|
+
return `${targetBase}+`;
|
|
382
|
+
if (modifier === '-')
|
|
383
|
+
return `${targetBase}-`;
|
|
384
|
+
return targetBase;
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* Drop a grade by N "letters." A "letter" here means a full base-grade step
|
|
388
|
+
* (A → B → C → F → F-), preserving the modifier when possible. So A+ dropped
|
|
389
|
+
* by 1 becomes B+, not A. Stops at F-.
|
|
390
|
+
*/
|
|
391
|
+
function dropGradeByLetters(grade, letters) {
|
|
392
|
+
if (letters <= 0 || grade === 'N/A' || grade === 'D')
|
|
393
|
+
return grade;
|
|
394
|
+
const baseLetter = baseGradeOf(grade);
|
|
395
|
+
const baseIdx = BASE_LETTERS.indexOf(baseLetter);
|
|
396
|
+
if (baseIdx === -1)
|
|
397
|
+
return grade;
|
|
398
|
+
const targetBaseIdx = baseIdx + letters;
|
|
399
|
+
// Past the F base — bottom out at F- (the absolute worst grade).
|
|
400
|
+
if (targetBaseIdx > 3)
|
|
401
|
+
return 'F-';
|
|
402
|
+
const targetBase = BASE_LETTERS[targetBaseIdx];
|
|
403
|
+
return applyModifierToTargetBase(targetBase, gradeModifier(grade));
|
|
404
|
+
}
|
|
405
|
+
function applyArchPenalty(dim, archFindings) {
|
|
406
|
+
const drop = archDropCount(archFindings);
|
|
407
|
+
if (drop === 0)
|
|
408
|
+
return dim;
|
|
409
|
+
const dropped = dropGradeByLetters(dim.grade, drop);
|
|
410
|
+
if (dropped === dim.grade)
|
|
411
|
+
return dim;
|
|
412
|
+
const archCount = archFindings.length;
|
|
413
|
+
const noun = pluralize('architectural finding', archCount);
|
|
414
|
+
const note = `dropped ${drop} ${pluralize('letter', drop)} by ${archCount} ${noun}`;
|
|
415
|
+
return {
|
|
416
|
+
...dim,
|
|
417
|
+
grade: dropped,
|
|
418
|
+
// Re-anchor score to the new band's midpoint so score and letter agree.
|
|
419
|
+
score: anchorScoreToGrade(dropped, dim.score),
|
|
420
|
+
rationale: dim.rationale === '0 maintainability findings' || dim.findingCount === 0
|
|
421
|
+
? note
|
|
422
|
+
: `${dim.rationale}; ${note}`,
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* Re-snap a score to fall within the band of the given grade. Used after
|
|
427
|
+
* applying the architectural penalty so the displayed score never disagrees
|
|
428
|
+
* with the displayed letter (e.g., grade C with score 89 would be jarring).
|
|
429
|
+
*
|
|
430
|
+
* If the original score is already in-band, keep it; otherwise pick the
|
|
431
|
+
* band's midpoint as a sensible default.
|
|
432
|
+
*/
|
|
433
|
+
function anchorScoreToGrade(grade, originalScore) {
|
|
434
|
+
if (grade === 'N/A' || grade === 'D')
|
|
435
|
+
return originalScore;
|
|
436
|
+
const ranges = {
|
|
437
|
+
'A+': [97, 100], A: [93, 96], 'A-': [90, 92],
|
|
438
|
+
'B+': [87, 89], B: [83, 86], 'B-': [80, 82],
|
|
439
|
+
'C+': [77, 79], C: [73, 76], 'C-': [70, 72],
|
|
440
|
+
'F+': [65, 69], F: [56, 64], 'F-': [0, 55],
|
|
441
|
+
};
|
|
442
|
+
const [lo, hi] = ranges[grade];
|
|
443
|
+
if (originalScore >= lo && originalScore <= hi)
|
|
444
|
+
return originalScore;
|
|
445
|
+
return Math.round((lo + hi) / 2);
|
|
446
|
+
}
|
|
447
|
+
// ============================================================================
|
|
448
|
+
// Grade Comparison Helpers
|
|
449
|
+
// ============================================================================
|
|
450
|
+
const BASE_RANK = { F: 1, C: 2, B: 3, A: 4 };
|
|
451
|
+
function baseIsWorse(a, b) {
|
|
452
|
+
return BASE_RANK[a] < BASE_RANK[b];
|
|
453
|
+
}
|
|
454
|
+
const FULL_RANK = {
|
|
455
|
+
'F-': 0, F: 1, 'F+': 2,
|
|
456
|
+
'C-': 3, C: 4, 'C+': 5,
|
|
457
|
+
'B-': 6, B: 7, 'B+': 8,
|
|
458
|
+
'A-': 9, A: 10, 'A+': 11,
|
|
459
|
+
};
|
|
460
|
+
function gradeRank(g) {
|
|
461
|
+
if (g === 'N/A')
|
|
462
|
+
return -1;
|
|
463
|
+
if (g === 'D')
|
|
464
|
+
return 1.5; // legacy: between F+ and C-
|
|
465
|
+
return FULL_RANK[g];
|
|
466
|
+
}
|
|
467
|
+
function worstOf(grades) {
|
|
468
|
+
let worst = 'A+';
|
|
469
|
+
for (const g of grades) {
|
|
470
|
+
if (g === 'N/A')
|
|
471
|
+
continue;
|
|
472
|
+
if (gradeRank(g) < gradeRank(worst))
|
|
473
|
+
worst = g;
|
|
474
|
+
}
|
|
475
|
+
return worst;
|
|
476
|
+
}
|
|
477
|
+
// ============================================================================
|
|
478
|
+
// Misc Helpers
|
|
479
|
+
// ============================================================================
|
|
480
|
+
function pluralize(word, n) {
|
|
481
|
+
return n === 1 ? word : `${word}s`;
|
|
482
|
+
}
|
|
483
|
+
function roundOne(n) {
|
|
484
|
+
return Math.round(n * 10) / 10;
|
|
485
|
+
}
|
|
486
|
+
function dimensionDisplayName(name) {
|
|
487
|
+
return name.charAt(0).toUpperCase() + name.slice(1);
|
|
488
|
+
}
|
|
489
|
+
function makeDimension(name, score, rationale, findingCount, worst) {
|
|
490
|
+
return {
|
|
491
|
+
name,
|
|
492
|
+
score,
|
|
493
|
+
grade: scoreToGrade(score),
|
|
494
|
+
rationale,
|
|
495
|
+
available: true,
|
|
496
|
+
findingCount,
|
|
497
|
+
worstSeverity: worst,
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
function naDimension(name) {
|
|
501
|
+
return {
|
|
502
|
+
name,
|
|
503
|
+
score: 0,
|
|
504
|
+
grade: 'N/A',
|
|
505
|
+
rationale: 'No tools available to evaluate',
|
|
506
|
+
available: false,
|
|
507
|
+
findingCount: 0,
|
|
508
|
+
worstSeverity: null,
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
// ============================================================================
|
|
512
|
+
// Top-Level Entry Point
|
|
513
|
+
// ============================================================================
|
|
514
|
+
function bucketByDimension(findings) {
|
|
515
|
+
const security = [];
|
|
516
|
+
const reliability = [];
|
|
517
|
+
const maintainability = [];
|
|
518
|
+
const architecture = [];
|
|
519
|
+
for (const f of findings) {
|
|
520
|
+
if (ARCHITECTURE_CATEGORIES.has(f.category))
|
|
521
|
+
architecture.push(f);
|
|
522
|
+
const dim = categoryToDimension(f.category);
|
|
523
|
+
if (dim === 'security')
|
|
524
|
+
security.push(f);
|
|
525
|
+
else if (dim === 'reliability')
|
|
526
|
+
reliability.push(f);
|
|
527
|
+
else
|
|
528
|
+
maintainability.push(f);
|
|
529
|
+
}
|
|
530
|
+
return { security, reliability, maintainability, architecture };
|
|
531
|
+
}
|
|
532
|
+
function isDimensionAvailable(dim, hasFindings, options) {
|
|
533
|
+
if (options?.forceNA?.has(dim))
|
|
534
|
+
return false;
|
|
535
|
+
const explicit = options?.availableDimensions;
|
|
536
|
+
if (explicit)
|
|
537
|
+
return explicit.has(dim);
|
|
538
|
+
// Auto-detect: maintainability always on, security/reliability iff findings exist.
|
|
539
|
+
return dim === 'maintainability' ? true : hasFindings;
|
|
540
|
+
}
|
|
541
|
+
/**
|
|
542
|
+
* Combine the available dimensions into a single overall grade + score.
|
|
543
|
+
*
|
|
544
|
+
* "Worst dimension wins" for the letter grade — a single failing dimension
|
|
545
|
+
* caps the overall score, matching how SonarQube's quality gate behaves.
|
|
546
|
+
* The numeric score is `min(avg, worst)` so a great Maintainability score
|
|
547
|
+
* can't paper over a Security failure.
|
|
548
|
+
*/
|
|
549
|
+
function computeOverall(availableDims) {
|
|
550
|
+
if (availableDims.length === 0) {
|
|
551
|
+
return { grade: 'N/A', score: 0 };
|
|
552
|
+
}
|
|
553
|
+
const grades = availableDims.map((d) => d.grade);
|
|
554
|
+
const scores = availableDims.map((d) => d.score);
|
|
555
|
+
const avg = scores.reduce((s, n) => s + n, 0) / scores.length;
|
|
556
|
+
const worst = worstOf(grades);
|
|
557
|
+
// Re-snap the displayed score so it lives in the worst dimension's band —
|
|
558
|
+
// otherwise we'd display a B-letter with a C-numeric score (or vice versa).
|
|
559
|
+
const blendedScore = Math.round(Math.min(avg, Math.min(...scores)));
|
|
560
|
+
return { grade: worst, score: anchorScoreToGrade(worst, blendedScore) };
|
|
561
|
+
}
|
|
562
|
+
export function computeQualityRating(allFindings, totalLines, options) {
|
|
563
|
+
const buckets = bucketByDimension(allFindings);
|
|
564
|
+
// Initial dimension grades, before architectural penalty.
|
|
565
|
+
const security = isDimensionAvailable('security', buckets.security.length > 0, options)
|
|
566
|
+
? gradeSecurity(buckets.security)
|
|
567
|
+
: naDimension('security');
|
|
568
|
+
const reliabilityRaw = isDimensionAvailable('reliability', buckets.reliability.length > 0, options)
|
|
569
|
+
? gradeReliability(buckets.reliability, totalLines)
|
|
570
|
+
: naDimension('reliability');
|
|
571
|
+
const maintainabilityRaw = isDimensionAvailable('maintainability', true, options)
|
|
572
|
+
? gradeMaintainability(buckets.maintainability, totalLines)
|
|
573
|
+
: naDimension('maintainability');
|
|
574
|
+
// Architectural penalty: hits whichever dimension(s) have arch findings
|
|
575
|
+
// bucketed into them (currently maintainability via the category map).
|
|
576
|
+
const archFindings = buckets.architecture;
|
|
577
|
+
const maintainability = maintainabilityRaw.available
|
|
578
|
+
? applyArchPenalty(maintainabilityRaw, archFindings)
|
|
579
|
+
: maintainabilityRaw;
|
|
580
|
+
const dimensions = [security, reliabilityRaw, maintainability];
|
|
581
|
+
const availableDims = dimensions.filter((d) => d.available);
|
|
582
|
+
const overall = computeOverall(availableDims);
|
|
583
|
+
const qualityGate = computeQualityGate(security, reliabilityRaw, archFindings.length);
|
|
584
|
+
const gradeRationale = computeGradeRationale(availableDims, overall.grade, allFindings.length);
|
|
585
|
+
return {
|
|
586
|
+
overall,
|
|
587
|
+
dimensions,
|
|
588
|
+
qualityGate,
|
|
589
|
+
gradeRationale,
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
// ============================================================================
|
|
593
|
+
// Quality Gate
|
|
594
|
+
// ============================================================================
|
|
595
|
+
/**
|
|
596
|
+
* The Quality Gate is a coarse PASS/FAIL signal layered on top of the grades.
|
|
597
|
+
* It only fires for the most user-actionable thresholds — any C-or-worse
|
|
598
|
+
* security grade, any F-tier reliability grade, or 2+ high-severity
|
|
599
|
+
* architectural findings. N/A dimensions never trigger a fail (we don't fail
|
|
600
|
+
* on missing data).
|
|
601
|
+
*/
|
|
602
|
+
function isFTier(g) {
|
|
603
|
+
return g === 'F+' || g === 'F' || g === 'F-' || g === 'D';
|
|
604
|
+
}
|
|
605
|
+
function isCorWorse(g) {
|
|
606
|
+
return baseGradeOf(g) === 'C' || isFTier(g);
|
|
607
|
+
}
|
|
608
|
+
function computeQualityGate(security, reliability, archFindingCount) {
|
|
609
|
+
const failingConditions = [];
|
|
610
|
+
if (security.available && isCorWorse(security.grade)) {
|
|
611
|
+
failingConditions.push(`Security grade ${security.grade} — ${security.rationale}`);
|
|
612
|
+
}
|
|
613
|
+
if (reliability.available && isFTier(reliability.grade)) {
|
|
614
|
+
failingConditions.push(`Reliability grade ${reliability.grade} — ${reliability.rationale}`);
|
|
615
|
+
}
|
|
616
|
+
if (archFindingCount >= 2) {
|
|
617
|
+
failingConditions.push(`${archFindingCount} architectural findings`);
|
|
618
|
+
}
|
|
619
|
+
return {
|
|
620
|
+
passed: failingConditions.length === 0,
|
|
621
|
+
failingConditions,
|
|
622
|
+
};
|
|
623
|
+
}
|
|
624
|
+
// ============================================================================
|
|
625
|
+
// Grade Rationale
|
|
626
|
+
// ============================================================================
|
|
627
|
+
function computeGradeRationale(availableDims, overallGrade, totalFindingCount) {
|
|
628
|
+
if (totalFindingCount === 0) {
|
|
629
|
+
return 'Clean — no findings detected';
|
|
630
|
+
}
|
|
631
|
+
if (availableDims.length === 0 || overallGrade === 'N/A') {
|
|
632
|
+
return 'No dimensions available to grade';
|
|
633
|
+
}
|
|
634
|
+
// All available dimensions share the same base letter -> "consistent
|
|
635
|
+
// quality". With +/- modifiers it's normal for sibling dimensions to land
|
|
636
|
+
// at A vs A+ depending on within-band position; calling that "inconsistent"
|
|
637
|
+
// would be misleading. We compare base letters so the user-facing message
|
|
638
|
+
// captures the high-level shape rather than every minor band difference.
|
|
639
|
+
const firstBase = baseGradeOf(availableDims[0].grade);
|
|
640
|
+
const allSameBase = availableDims.every((d) => baseGradeOf(d.grade) === firstBase);
|
|
641
|
+
if (allSameBase) {
|
|
642
|
+
return `All dimensions ${firstBase}-tier — consistent quality`;
|
|
643
|
+
}
|
|
644
|
+
// Find the dimension that pinned the overall grade (worst available).
|
|
645
|
+
const worstDim = availableDims.find((d) => d.grade === overallGrade) ??
|
|
646
|
+
// Fallback shouldn't fire since overallGrade was derived from availableDims.
|
|
647
|
+
availableDims[0];
|
|
648
|
+
return `Capped at ${overallGrade} by ${dimensionDisplayName(worstDim.name)} (${worstDim.rationale})`;
|
|
649
|
+
}
|
|
650
|
+
//# sourceMappingURL=quality-grading.js.map
|