@minionry/minion 0.7.36
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 +14 -0
- package/PRIVACY.md +357 -0
- package/README.md +57 -0
- package/THIRD_PARTY_NOTICES.md +245 -0
- package/bin/branding.js +4 -0
- package/bin/commands/browser-install.js +483 -0
- package/bin/commands/create-sandbox.js +542 -0
- package/bin/commands/fingerprint.js +101 -0
- package/bin/commands/login.js +400 -0
- package/bin/commands/logout.js +93 -0
- package/bin/commands/minions.js +211 -0
- package/bin/commands/sandboxes.js +309 -0
- package/bin/commands/status.js +298 -0
- package/bin/commands/whoami.js +153 -0
- package/bin/launch-env.js +35 -0
- package/bin/machine-identity.js +221 -0
- package/bin/machine-layout.js +200 -0
- package/bin/minion.js +839 -0
- package/bin/platform-endpoints.js +8 -0
- package/bin/postinstall.js +52 -0
- package/bin/pty-doctor.js +326 -0
- package/bin/pty-locator.js +110 -0
- package/bin/pty-provision.js +186 -0
- package/bin/sandbox/Dockerfile +78 -0
- package/bin/sandbox/entrypoint.sh +14 -0
- package/bin/sandbox/install-repo.sh +53 -0
- package/bin/sandbox/seccomp-userns.json +883 -0
- package/bin/sandbox-config.js +306 -0
- package/dist/bin/machine-identity.js +221 -0
- package/dist/bin/machine-layout.js +200 -0
- package/dist/electron-host/agent-lease.js +141 -0
- package/dist/electron-host/dom-driver.js +642 -0
- package/dist/electron-host/ipc-protocol.js +89 -0
- package/dist/electron-host/js-driver.js +216 -0
- package/dist/electron-host/view-manager.js +254 -0
- package/dist/server/cli/engine-resume.js +49 -0
- package/dist/server/cli/eta-estimator.js +278 -0
- package/dist/server/cli/headless/background-activity.js +109 -0
- package/dist/server/cli/headless/claude-invoker-process.js +614 -0
- package/dist/server/cli/headless/claude-invoker-stall.js +150 -0
- package/dist/server/cli/headless/claude-invoker-stream.js +555 -0
- package/dist/server/cli/headless/claude-invoker-tools.js +148 -0
- package/dist/server/cli/headless/claude-invoker.js +151 -0
- package/dist/server/cli/headless/engine-availability.js +92 -0
- package/dist/server/cli/headless/engine-notice.js +185 -0
- package/dist/server/cli/headless/haiku-assessments.js +293 -0
- package/dist/server/cli/headless/headless-logger.js +75 -0
- package/dist/server/cli/headless/index.js +9 -0
- package/dist/server/cli/headless/mcp-config.js +314 -0
- package/dist/server/cli/headless/native-timeout-detector.js +72 -0
- package/dist/server/cli/headless/output-utils.js +71 -0
- package/dist/server/cli/headless/prompt-utils.js +82 -0
- package/dist/server/cli/headless/resilient-runner.js +326 -0
- package/dist/server/cli/headless/retry-strategies.js +292 -0
- package/dist/server/cli/headless/runner.js +245 -0
- package/dist/server/cli/headless/stall-assessor.js +295 -0
- package/dist/server/cli/headless/tool-watchdog.js +447 -0
- package/dist/server/cli/headless/types.js +18 -0
- package/dist/server/cli/history-portability.js +85 -0
- package/dist/server/cli/improvisation-attachments.js +99 -0
- package/dist/server/cli/improvisation-history-store.js +121 -0
- package/dist/server/cli/improvisation-movements.js +137 -0
- package/dist/server/cli/improvisation-output-queue.js +34 -0
- package/dist/server/cli/improvisation-retry.js +8 -0
- package/dist/server/cli/improvisation-session-manager.js +1016 -0
- package/dist/server/cli/improvisation-types.js +31 -0
- package/dist/server/cli/plan-lifecycle.js +72 -0
- package/dist/server/cli/prompt-builders.js +371 -0
- package/dist/server/cli/retry/retry-best-result.js +28 -0
- package/dist/server/cli/retry/retry-context-loss.js +75 -0
- package/dist/server/cli/retry/retry-recovery-strategies.js +213 -0
- package/dist/server/cli/retry/retry-resume-strategy.js +20 -0
- package/dist/server/cli/retry/retry-runner-factory.js +92 -0
- package/dist/server/cli/retry/retry-tool-results.js +22 -0
- package/dist/server/cli/retry/retry-types.js +3 -0
- package/dist/server/engines/EngineEvent.js +57 -0
- package/dist/server/engines/claude/ClaudeCodeEngine.js +316 -0
- package/dist/server/engines/claude/claude-command.js +32 -0
- package/dist/server/engines/codex/CodexEngine.js +469 -0
- package/dist/server/engines/codex/codex-auth.js +203 -0
- package/dist/server/engines/codex/codex-invoker.js +205 -0
- package/dist/server/engines/codex/codex-provision.js +215 -0
- package/dist/server/engines/credentials/credential-probe.js +88 -0
- package/dist/server/engines/credentials/model-profiles.js +249 -0
- package/dist/server/engines/credentials/provider-credentials.js +146 -0
- package/dist/server/engines/engine-event-stream.js +52 -0
- package/dist/server/engines/execution/engine-session.js +380 -0
- package/dist/server/engines/factory.js +42 -0
- package/dist/server/engines/hermes/HermesEngine.js +628 -0
- package/dist/server/engines/hermes/hermes-auth.js +159 -0
- package/dist/server/engines/hermes/hermes-gateway-home.js +271 -0
- package/dist/server/engines/hermes/hermes-invoker.js +350 -0
- package/dist/server/engines/hermes/hermes-provision.js +318 -0
- package/dist/server/engines/read-only.js +20 -0
- package/dist/server/engines/types.js +27 -0
- package/dist/server/handlers/company-execution-registry.js +67 -0
- package/dist/server/handlers/company-handlers.js +514 -0
- package/dist/server/handlers/company-triggers.js +189 -0
- package/dist/server/handlers/company-types.js +3 -0
- package/dist/server/index.js +244 -0
- package/dist/server/mcp/app-tools-bridge.js +110 -0
- package/dist/server/mcp/app-tools-mcp-server.js +99 -0
- package/dist/server/mcp/bouncer-integration.js +744 -0
- package/dist/server/mcp/browser-mcp-server.js +915 -0
- package/dist/server/mcp/browser-redaction.js +103 -0
- package/dist/server/mcp/classifier/BouncerClassifier.js +3 -0
- package/dist/server/mcp/classifier/ClaudeBouncerClassifier.js +212 -0
- package/dist/server/mcp/classifier/factory.js +124 -0
- package/dist/server/mcp/classifier/health.js +54 -0
- package/dist/server/mcp/classifier/telemetry.js +0 -0
- package/dist/server/mcp/company-patterns.js +104 -0
- package/dist/server/mcp/department-profiles.js +133 -0
- package/dist/server/mcp/quality-bridge.js +153 -0
- package/dist/server/mcp/quality-mcp-server.js +105 -0
- package/dist/server/mcp/security-analysis.js +384 -0
- package/dist/server/mcp/security-audit.js +152 -0
- package/dist/server/mcp/security-patterns.js +314 -0
- package/dist/server/mcp/server.js +194 -0
- package/dist/server/mcp/test-utils/agent-simulator.js +301 -0
- package/dist/server/mcp/test-utils/e2e-agent.js +103 -0
- package/dist/server/mcp/test-utils/e2e-capture.js +92 -0
- package/dist/server/mcp/test-utils/e2e-client.js +116 -0
- package/dist/server/mcp/test-utils/e2e-fixtures.js +87 -0
- package/dist/server/mcp/test-utils/e2e-gating.js +8 -0
- package/dist/server/mcp/test-utils/e2e-harness.js +8 -0
- package/dist/server/mcp/test-utils/e2e-scenarios.js +105 -0
- package/dist/server/routes/files.js +18 -0
- package/dist/server/routes/improvise.js +64 -0
- package/dist/server/routes/index.js +7 -0
- package/dist/server/routes/instances.js +41 -0
- package/dist/server/routes/internal.js +900 -0
- package/dist/server/routes/notifications.js +110 -0
- package/dist/server/server-setup.js +282 -0
- package/dist/server/services/agent-push.js +148 -0
- package/dist/server/services/analytics.js +197 -0
- package/dist/server/services/apps/build.js +249 -0
- package/dist/server/services/apps/greenlight.js +199 -0
- package/dist/server/services/apps/install.js +101 -0
- package/dist/server/services/apps/layout.js +35 -0
- package/dist/server/services/apps/serve.js +665 -0
- package/dist/server/services/apps/space-apps.js +248 -0
- package/dist/server/services/auth.js +48 -0
- package/dist/server/services/browser/chrome-sandbox.js +245 -0
- package/dist/server/services/browser/chromium-launch-spec.js +51 -0
- package/dist/server/services/browser/claude-reasoner.js +669 -0
- package/dist/server/services/browser/debug-log.js +3 -0
- package/dist/server/services/browser/driver-state.js +546 -0
- package/dist/server/services/browser/electron-provision.js +256 -0
- package/dist/server/services/browser/host-internal.js +245 -0
- package/dist/server/services/browser/host-mock.js +334 -0
- package/dist/server/services/browser/host-types.js +3 -0
- package/dist/server/services/browser/host.js +1067 -0
- package/dist/server/services/browser/index.js +4 -0
- package/dist/server/services/browser/ipc-client.js +354 -0
- package/dist/server/services/browser/pipewire-sink.js +199 -0
- package/dist/server/services/browser/preflight.js +590 -0
- package/dist/server/services/browser/reasoning-agent.js +659 -0
- package/dist/server/services/browser/sandbox-policy.js +169 -0
- package/dist/server/services/browser/stderr-noise-filter.js +26 -0
- package/dist/server/services/browser/telemetry.js +107 -0
- package/dist/server/services/browser/xvfb-supervisor.js +139 -0
- package/dist/server/services/browser-frame-policy.js +56 -0
- package/dist/server/services/client-id.js +32 -0
- package/dist/server/services/collab/registry.js +408 -0
- package/dist/server/services/collab/session.js +489 -0
- package/dist/server/services/collab/text-diff.js +28 -0
- package/dist/server/services/collab/types.js +19 -0
- package/dist/server/services/e2ee-group-key.js +197 -0
- package/dist/server/services/e2ee-identity.js +203 -0
- package/dist/server/services/file-explorer-ops.js +328 -0
- package/dist/server/services/file-transfer-zip.js +188 -0
- package/dist/server/services/files.js +111 -0
- package/dist/server/services/git/branch-ops.js +62 -0
- package/dist/server/services/git/command.js +68 -0
- package/dist/server/services/git/commit-message.js +74 -0
- package/dist/server/services/git/diff-ops.js +56 -0
- package/dist/server/services/git/haiku.js +76 -0
- package/dist/server/services/git/head-watcher.js +226 -0
- package/dist/server/services/git/index.js +17 -0
- package/dist/server/services/git/log-ops.js +98 -0
- package/dist/server/services/git/merge-ops.js +314 -0
- package/dist/server/services/git/parsers.js +401 -0
- package/dist/server/services/git/remote-ops.js +192 -0
- package/dist/server/services/git/status-ops.js +105 -0
- package/dist/server/services/git/suggestion-ops.js +80 -0
- package/dist/server/services/git/tag-ops.js +36 -0
- package/dist/server/services/git/types.js +4 -0
- package/dist/server/services/git/worktree-ops.js +114 -0
- package/dist/server/services/instances.js +337 -0
- package/dist/server/services/model-catalog.js +247 -0
- package/dist/server/services/observability/posthog.js +153 -0
- package/dist/server/services/pathUtils.js +114 -0
- package/dist/server/services/plan/agent-loader.js +97 -0
- package/dist/server/services/plan/agent-manager.js +363 -0
- package/dist/server/services/plan/agent-resolver.js +74 -0
- package/dist/server/services/plan/agents/assess-stall.md +33 -0
- package/dist/server/services/plan/agents/browser-planner.md +53 -0
- package/dist/server/services/plan/agents/browser-reasoner.md +114 -0
- package/dist/server/services/plan/agents/check-injection.md +95 -0
- package/dist/server/services/plan/agents/classify-error.md +30 -0
- package/dist/server/services/plan/agents/code-review.md +142 -0
- package/dist/server/services/plan/agents/commit-message.md +28 -0
- package/dist/server/services/plan/agents/detect-context-loss.md +30 -0
- package/dist/server/services/plan/agents/execute-issue.md +58 -0
- package/dist/server/services/plan/agents/fix-quality.md +25 -0
- package/dist/server/services/plan/agents/plan-coordinator.md +72 -0
- package/dist/server/services/plan/agents/pr-description.md +29 -0
- package/dist/server/services/plan/agents/retry-task.md +27 -0
- package/dist/server/services/plan/agents/review-code.md +38 -0
- package/dist/server/services/plan/agents/review-quality.md +45 -0
- package/dist/server/services/plan/agents/triage-findings.md +78 -0
- package/dist/server/services/plan/agents/verify-review.md +57 -0
- package/dist/server/services/plan/approval-evaluator.js +143 -0
- package/dist/server/services/plan/board-archive.js +102 -0
- package/dist/server/services/plan/board-config.js +108 -0
- package/dist/server/services/plan/board-export.js +214 -0
- package/dist/server/services/plan/composer-prompt.js +115 -0
- package/dist/server/services/plan/composer.js +664 -0
- package/dist/server/services/plan/config-installer.js +103 -0
- package/dist/server/services/plan/dependency-resolver.js +137 -0
- package/dist/server/services/plan/executor.js +696 -0
- package/dist/server/services/plan/front-matter.js +43 -0
- package/dist/server/services/plan/issue-classification.js +10 -0
- package/dist/server/services/plan/issue-effort.js +182 -0
- package/dist/server/services/plan/issue-loader.js +35 -0
- package/dist/server/services/plan/issue-prompt-builder.js +152 -0
- package/dist/server/services/plan/issue-retry.js +42 -0
- package/dist/server/services/plan/issue-writer.js +102 -0
- package/dist/server/services/plan/metrics-collector.js +272 -0
- package/dist/server/services/plan/output-manager.js +77 -0
- package/dist/server/services/plan/parser-core.js +435 -0
- package/dist/server/services/plan/parser-migration.js +108 -0
- package/dist/server/services/plan/parser.js +258 -0
- package/dist/server/services/plan/plan-runner-factory.js +68 -0
- package/dist/server/services/plan/pm-timeline.js +320 -0
- package/dist/server/services/plan/progress-log.js +101 -0
- package/dist/server/services/plan/prompt-builder.js +142 -0
- package/dist/server/services/plan/quality-delta.js +274 -0
- package/dist/server/services/plan/readiness-planner.js +34 -0
- package/dist/server/services/plan/review-gate.js +409 -0
- package/dist/server/services/plan/review-report.js +134 -0
- package/dist/server/services/plan/review-verdict-stream.js +161 -0
- package/dist/server/services/plan/state-reconciler.js +238 -0
- package/dist/server/services/plan/template-diff.js +175 -0
- package/dist/server/services/plan/template-instantiator.js +516 -0
- package/dist/server/services/plan/template-manifest.js +56 -0
- package/dist/server/services/plan/template-types.js +3 -0
- package/dist/server/services/plan/trigger-evaluator.js +211 -0
- package/dist/server/services/plan/types.js +3 -0
- package/dist/server/services/plan/watcher.js +73 -0
- package/dist/server/services/platform-credentials.js +56 -0
- package/dist/server/services/platform-e2ee.js +421 -0
- package/dist/server/services/platform-envelope.js +102 -0
- package/dist/server/services/platform-heartbeat.js +88 -0
- package/dist/server/services/platform-reconnect.js +60 -0
- package/dist/server/services/platform-token-lifecycle.js +95 -0
- package/dist/server/services/platform-url.js +17 -0
- package/dist/server/services/platform.js +656 -0
- package/dist/server/services/runtime-info.js +9 -0
- package/dist/server/services/schedule/agent-schedule-store.js +232 -0
- package/dist/server/services/schedule/agent-scheduler.js +323 -0
- package/dist/server/services/schedule/daily-timing.js +105 -0
- package/dist/server/services/schedule/quality-schedule-launch.js +11 -0
- package/dist/server/services/schedule/quality-schedule-store.js +211 -0
- package/dist/server/services/schedule/quality-scheduler.js +215 -0
- package/dist/server/services/schedule/run-bootstrap.js +329 -0
- package/dist/server/services/schedule/schedule-store.js +238 -0
- package/dist/server/services/schedule/schedule-wire.js +77 -0
- package/dist/server/services/schedule/scheduler.js +310 -0
- package/dist/server/services/schedule/template-source.js +97 -0
- package/dist/server/services/sdk/agent-sessions.js +264 -0
- package/dist/server/services/sdk/agents.js +557 -0
- package/dist/server/services/sdk/app-endpoints.js +48 -0
- package/dist/server/services/sdk/app-tools.js +303 -0
- package/dist/server/services/sdk/audit.js +48 -0
- package/dist/server/services/sdk/browser-transport.js +188 -0
- package/dist/server/services/sdk/browser.js +605 -0
- package/dist/server/services/sdk/composer.js +129 -0
- package/dist/server/services/sdk/connect.js +273 -0
- package/dist/server/services/sdk/consent-cooldown.js +31 -0
- package/dist/server/services/sdk/endpoint-url.js +141 -0
- package/dist/server/services/sdk/endpoints.js +314 -0
- package/dist/server/services/sdk/file-change-notify.js +36 -0
- package/dist/server/services/sdk/files-app.js +163 -0
- package/dist/server/services/sdk/files-search.js +131 -0
- package/dist/server/services/sdk/files-transfer.js +279 -0
- package/dist/server/services/sdk/files-workspace.js +233 -0
- package/dist/server/services/sdk/git.js +520 -0
- package/dist/server/services/sdk/grant-store.js +318 -0
- package/dist/server/services/sdk/host-ops.js +84 -0
- package/dist/server/services/sdk/inference-claude.js +214 -0
- package/dist/server/services/sdk/inference.js +247 -0
- package/dist/server/services/sdk/live-registry.js +42 -0
- package/dist/server/services/sdk/local-app-bindings.js +71 -0
- package/dist/server/services/sdk/pipeline.js +190 -0
- package/dist/server/services/sdk/pm-execute.js +228 -0
- package/dist/server/services/sdk/pm-planning-tap.js +29 -0
- package/dist/server/services/sdk/pm-schedule.js +165 -0
- package/dist/server/services/sdk/pm.js +634 -0
- package/dist/server/services/sdk/quality-change-notify.js +20 -0
- package/dist/server/services/sdk/quality-schedule.js +165 -0
- package/dist/server/services/sdk/quality.js +602 -0
- package/dist/server/services/sdk/rate-limits.js +198 -0
- package/dist/server/services/sdk/registry.js +597 -0
- package/dist/server/services/sdk/revoke.js +86 -0
- package/dist/server/services/sdk/run-profile.js +41 -0
- package/dist/server/services/sdk/sandbox.js +89 -0
- package/dist/server/services/sdk/storage.js +110 -0
- package/dist/server/services/sdk/streams.js +147 -0
- package/dist/server/services/sdk/terminal.js +201 -0
- package/dist/server/services/sdk/test-utils/workshop-browser-app.js +466 -0
- package/dist/server/services/sentry.js +99 -0
- package/dist/server/services/settings.js +374 -0
- package/dist/server/services/snippets/expander.js +87 -0
- package/dist/server/services/snippets/index.js +6 -0
- package/dist/server/services/snippets/loader.js +119 -0
- package/dist/server/services/snippets/parser.js +52 -0
- package/dist/server/services/snippets/types.js +3 -0
- package/dist/server/services/snippets/watcher.js +60 -0
- package/dist/server/services/space-layout.js +80 -0
- package/dist/server/services/system-prompts.js +219 -0
- package/dist/server/services/terminal/pty-manager.js +206 -0
- package/dist/server/services/terminal/pty-utils.js +150 -0
- package/dist/server/services/timeline/assembler.js +890 -0
- package/dist/server/services/timeline/contract.js +196 -0
- package/dist/server/services/timeline/index.js +4 -0
- package/dist/server/services/websocket/agent-schedule-handlers.js +217 -0
- package/dist/server/services/websocket/apps-handlers.js +399 -0
- package/dist/server/services/websocket/ask-user-question-bridge.js +112 -0
- package/dist/server/services/websocket/autocomplete-index.js +415 -0
- package/dist/server/services/websocket/autocomplete-match.js +243 -0
- package/dist/server/services/websocket/autocomplete.js +407 -0
- package/dist/server/services/websocket/browser-agent-runtime.js +208 -0
- package/dist/server/services/websocket/browser-device-profile.js +46 -0
- package/dist/server/services/websocket/browser-handlers.js +377 -0
- package/dist/server/services/websocket/browser-host-broadcast.js +179 -0
- package/dist/server/services/websocket/browser-ice-config.js +121 -0
- package/dist/server/services/websocket/browser-payloads.js +222 -0
- package/dist/server/services/websocket/browser-viewer-lifecycle.js +84 -0
- package/dist/server/services/websocket/browser-viewer-registry.js +118 -0
- package/dist/server/services/websocket/collab-broadcast.js +11 -0
- package/dist/server/services/websocket/collab-handlers.js +116 -0
- package/dist/server/services/websocket/commit-suggestion-service.js +132 -0
- package/dist/server/services/websocket/debug-log.js +38 -0
- package/dist/server/services/websocket/dispatch-util.js +10 -0
- package/dist/server/services/websocket/engine-auth-handlers.js +323 -0
- package/dist/server/services/websocket/engine-credential-watcher.js +83 -0
- package/dist/server/services/websocket/engine-profile-handlers.js +92 -0
- package/dist/server/services/websocket/file-definition-handlers.js +180 -0
- package/dist/server/services/websocket/file-download-handler.js +291 -0
- package/dist/server/services/websocket/file-explorer-handlers.js +291 -0
- package/dist/server/services/websocket/file-search-handlers.js +267 -0
- package/dist/server/services/websocket/file-share-handlers.js +328 -0
- package/dist/server/services/websocket/file-transfer-http.js +361 -0
- package/dist/server/services/websocket/file-upload-handler.js +267 -0
- package/dist/server/services/websocket/file-utils.js +346 -0
- package/dist/server/services/websocket/git-branch-handlers.js +90 -0
- package/dist/server/services/websocket/git-diff-handlers.js +59 -0
- package/dist/server/services/websocket/git-handlers.js +212 -0
- package/dist/server/services/websocket/git-head-watcher.js +85 -0
- package/dist/server/services/websocket/git-log-handlers.js +104 -0
- package/dist/server/services/websocket/git-pr-handlers.js +56 -0
- package/dist/server/services/websocket/git-tag-handlers.js +53 -0
- package/dist/server/services/websocket/git-utils.js +11 -0
- package/dist/server/services/websocket/git-worktree-handlers.js +521 -0
- package/dist/server/services/websocket/handler-context.js +113 -0
- package/dist/server/services/websocket/handler.js +1197 -0
- package/dist/server/services/websocket/index.js +5 -0
- package/dist/server/services/websocket/movement-written-files.js +26 -0
- package/dist/server/services/websocket/msg-id-tracker.js +48 -0
- package/dist/server/services/websocket/plan-board-handlers.js +410 -0
- package/dist/server/services/websocket/plan-execution-handlers.js +244 -0
- package/dist/server/services/websocket/plan-handlers.js +67 -0
- package/dist/server/services/websocket/plan-helpers.js +161 -0
- package/dist/server/services/websocket/plan-issue-handlers.js +168 -0
- package/dist/server/services/websocket/plan-sprint-handlers.js +190 -0
- package/dist/server/services/websocket/plan-transfer-handlers.js +310 -0
- package/dist/server/services/websocket/pm-timeline-broadcast.js +28 -0
- package/dist/server/services/websocket/post-session-scan.js +132 -0
- package/dist/server/services/websocket/prompt-queue-handlers.js +96 -0
- package/dist/server/services/websocket/quality-agent-cost.js +229 -0
- package/dist/server/services/websocket/quality-agent-notes.js +113 -0
- package/dist/server/services/websocket/quality-change-activity.js +187 -0
- package/dist/server/services/websocket/quality-complexity.js +345 -0
- package/dist/server/services/websocket/quality-dependencies.js +445 -0
- package/dist/server/services/websocket/quality-duplication.js +162 -0
- package/dist/server/services/websocket/quality-eta.js +65 -0
- package/dist/server/services/websocket/quality-fingerprint.js +104 -0
- package/dist/server/services/websocket/quality-grading.js +575 -0
- package/dist/server/services/websocket/quality-handlers.js +48 -0
- package/dist/server/services/websocket/quality-linting.js +270 -0
- package/dist/server/services/websocket/quality-operations.js +54 -0
- package/dist/server/services/websocket/quality-persistence.js +410 -0
- package/dist/server/services/websocket/quality-review-agent.js +526 -0
- package/dist/server/services/websocket/quality-run.js +220 -0
- package/dist/server/services/websocket/quality-secrets.js +0 -0
- package/dist/server/services/websocket/quality-service.js +775 -0
- package/dist/server/services/websocket/quality-tools.js +383 -0
- package/dist/server/services/websocket/quality-triage-agent.js +101 -0
- package/dist/server/services/websocket/quality-types.js +149 -0
- package/dist/server/services/websocket/recommended-answers.js +28 -0
- package/dist/server/services/websocket/schedule-handlers.js +197 -0
- package/dist/server/services/websocket/sdk-agent-host.js +569 -0
- package/dist/server/services/websocket/sdk-browser-host.js +459 -0
- package/dist/server/services/websocket/sdk-composer-host.js +10 -0
- package/dist/server/services/websocket/sdk-git-host.js +115 -0
- package/dist/server/services/websocket/sdk-handlers.js +242 -0
- package/dist/server/services/websocket/sdk-pm-execution-host.js +63 -0
- package/dist/server/services/websocket/sdk-pm-schedule-host.js +21 -0
- package/dist/server/services/websocket/sdk-quality-schedule-host.js +34 -0
- package/dist/server/services/websocket/sdk-terminal-host.js +140 -0
- package/dist/server/services/websocket/session-handlers.js +565 -0
- package/dist/server/services/websocket/session-history.js +335 -0
- package/dist/server/services/websocket/session-initialization.js +315 -0
- package/dist/server/services/websocket/session-registry.js +340 -0
- package/dist/server/services/websocket/session-role-directory.js +39 -0
- package/dist/server/services/websocket/settings-handlers.js +276 -0
- package/dist/server/services/websocket/skill-handlers.js +324 -0
- package/dist/server/services/websocket/skill-watcher.js +83 -0
- package/dist/server/services/websocket/snippet-handlers.js +261 -0
- package/dist/server/services/websocket/tab-broadcast.js +35 -0
- package/dist/server/services/websocket/tab-event-buffer.js +110 -0
- package/dist/server/services/websocket/tab-event-replay.js +47 -0
- package/dist/server/services/websocket/tab-handlers.js +211 -0
- package/dist/server/services/websocket/terminal-handlers.js +268 -0
- package/dist/server/services/websocket/types.js +96 -0
- package/dist/server/types/company-messages.js +102 -0
- package/dist/server/utils/agent-manager.js +202 -0
- package/dist/server/utils/console-timestamps.js +24 -0
- package/dist/server/utils/file-store.js +112 -0
- package/dist/server/utils/paths.js +28 -0
- package/dist/server/utils/port-manager.js +43 -0
- package/dist/server/utils/port.js +204 -0
- package/electron-host/dist/agent-lease.js +141 -0
- package/electron-host/dist/capture-recovery.js +77 -0
- package/electron-host/dist/codec-policy.js +37 -0
- package/electron-host/dist/controller-channel.js +60 -0
- package/electron-host/dist/controller-handlers.js +153 -0
- package/electron-host/dist/device-emulator.js +106 -0
- package/electron-host/dist/dom-driver.js +642 -0
- package/electron-host/dist/domain-registry.js +159 -0
- package/electron-host/dist/domain-validators.js +308 -0
- package/electron-host/dist/editable-focus-reporter.js +143 -0
- package/electron-host/dist/fake-peer.test-util.js +105 -0
- package/electron-host/dist/host-log.js +31 -0
- package/electron-host/dist/host-wire.js +11 -0
- package/electron-host/dist/input-router.js +421 -0
- package/electron-host/dist/ipc-protocol.js +89 -0
- package/electron-host/dist/ipc-server.js +193 -0
- package/electron-host/dist/js-driver.js +216 -0
- package/electron-host/dist/main-guards.js +70 -0
- package/electron-host/dist/main.js +716 -0
- package/electron-host/dist/roster-sync.js +87 -0
- package/electron-host/dist/rpc-context.js +1 -0
- package/electron-host/dist/rpc-tab-methods.js +138 -0
- package/electron-host/dist/rpc-webrtc-methods.js +121 -0
- package/electron-host/dist/screen-capture-cache.js +147 -0
- package/electron-host/dist/sdk-injector.js +166 -0
- package/electron-host/dist/sdp.js +102 -0
- package/electron-host/dist/session-surface.js +78 -0
- package/electron-host/dist/surface-sizing.js +59 -0
- package/electron-host/dist/view-manager.js +254 -0
- package/electron-host/dist/viewer-registry.js +46 -0
- package/electron-host/dist/viewer-session.js +288 -0
- package/electron-host/dist/webrtc-channels.js +14 -0
- package/electron-host/dist/webrtc-controller.js +57 -0
- package/electron-host/dist/webrtc-ice-filter.js +13 -0
- package/electron-host/dist/webrtc-peer.js +45 -0
- package/electron-host/package.json +12 -0
- package/electron-host/tsconfig.json +23 -0
- package/electron-host/webrtc-controller-preload.cjs +3 -0
- package/electron-host/webrtc-controller.html +14 -0
- package/package.json +85 -0
- package/vendor/e2ee/LICENSE +14 -0
- package/vendor/e2ee/package.json +14 -0
- package/vendor/e2ee/src/index.js +310 -0
|
@@ -0,0 +1,775 @@
|
|
|
1
|
+
// Copyright (c) 2025-present Minionry, Inc.
|
|
2
|
+
// SPDX-License-Identifier: LicenseRef-Minionry-Software
|
|
3
|
+
import { existsSync } from 'node:fs';
|
|
4
|
+
import { extname } from 'node:path';
|
|
5
|
+
import { ENGINE_EFFORT_LEVELS, engineForModel } from '../../engines/types.js';
|
|
6
|
+
import { getQualityEffortLevel, getQualityEngine, getQualityFastMode, getQualityModel } from '../settings.js';
|
|
7
|
+
import { analyzeComplexity, analyzeFunctionLength, STRUCTURAL_EXTENSIONS } from './quality-complexity.js';
|
|
8
|
+
import { analyzeDependencies } from './quality-dependencies.js';
|
|
9
|
+
import { analyzeDuplication } from './quality-duplication.js';
|
|
10
|
+
import { collectedContentReader, normalizeFindingPath, stampFingerprints } from './quality-fingerprint.js';
|
|
11
|
+
import { computeQualityRating, computeReadiness, gradeFromScore } from './quality-grading.js';
|
|
12
|
+
import { analyzeLinting, cargoWorkspaceRoot } from './quality-linting.js';
|
|
13
|
+
import { analyzeSecrets } from './quality-secrets.js';
|
|
14
|
+
import { chunkFileList, classifyFileRole, collectSourceFiles, detectEcosystem, filesByExt, isTestFile, readSourceFiles, runCommand } from './quality-tools.js';
|
|
15
|
+
import { runTriagePass } from './quality-triage-agent.js';
|
|
16
|
+
import { ANALYZER_FINDING_BOUND, ECOSYSTEM_TOOLS, FILE_LENGTH_THRESHOLD, findingCountsTowardGrade, hasInstalledToolInCategory, STRUCTURAL_CATEGORIES, TOTAL_STEPS } from './quality-types.js';
|
|
17
|
+
const NODE_FMT_EXTS = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs'];
|
|
18
|
+
const PY_FMT_EXTS = ['.py', '.pyi'];
|
|
19
|
+
const TRIAGE_STEP = 11;
|
|
20
|
+
const SCORING_STEP = 12;
|
|
21
|
+
export { detectEcosystem, detectTools, installTools } from './quality-tools.js';
|
|
22
|
+
export function resolveQualityModels(sel) {
|
|
23
|
+
const engine = sel.engine ?? 'claude-code';
|
|
24
|
+
const effortLevel = sel.effortLevel && sel.effortLevel !== 'auto' && (ENGINE_EFFORT_LEVELS[engine] ?? []).includes(sel.effortLevel)
|
|
25
|
+
? sel.effortLevel
|
|
26
|
+
: undefined;
|
|
27
|
+
const fastMode = engine === 'claude-code' ? sel.fastMode === true : false;
|
|
28
|
+
const model = sel.model && sel.model !== 'default' && engineForModel(sel.model) === engine ? sel.model : undefined;
|
|
29
|
+
if (engine === 'claude-code' && !model) {
|
|
30
|
+
return { engine, reviewModel: undefined, triageModel: 'haiku', effortLevel, fastMode };
|
|
31
|
+
}
|
|
32
|
+
return { engine, reviewModel: model, triageModel: model, effortLevel, fastMode };
|
|
33
|
+
}
|
|
34
|
+
export function getQualityModels() {
|
|
35
|
+
return resolveQualityModels({
|
|
36
|
+
engine: getQualityEngine(),
|
|
37
|
+
model: getQualityModel(),
|
|
38
|
+
effortLevel: getQualityEffortLevel(),
|
|
39
|
+
fastMode: getQualityFastMode(),
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
function backgroundTriageModels() {
|
|
43
|
+
const models = getQualityModels();
|
|
44
|
+
return models.engine === 'claude-code' ? { ...models, triageModel: 'haiku', effortLevel: undefined, fastMode: false } : models;
|
|
45
|
+
}
|
|
46
|
+
function failureDetail(result) {
|
|
47
|
+
const stderr = result.stderr.split('\n').map((l) => l.trim()).find(Boolean);
|
|
48
|
+
if (stderr)
|
|
49
|
+
return stderr;
|
|
50
|
+
return result.stdout.split('\n').map((l) => l.trim()).filter(Boolean).pop() ?? '';
|
|
51
|
+
}
|
|
52
|
+
function exitNote(tool, result) {
|
|
53
|
+
const detail = failureDetail(result);
|
|
54
|
+
return `${tool} exited ${result.exitCode}${detail ? `: ${detail}` : ''}`;
|
|
55
|
+
}
|
|
56
|
+
function newFmtAccumulator() {
|
|
57
|
+
return { totalFiles: 0, passingFiles: 0, ran: false, findings: [], notRun: [] };
|
|
58
|
+
}
|
|
59
|
+
async function fmtNode(dirPath, files, acc) {
|
|
60
|
+
const targets = filesByExt(files, NODE_FMT_EXTS);
|
|
61
|
+
if (targets.length === 0) {
|
|
62
|
+
acc.notRun.push('no JavaScript or TypeScript files to format');
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
acc.totalFiles += targets.length;
|
|
66
|
+
const unformattedSet = new Set();
|
|
67
|
+
for (const chunk of chunkFileList(targets)) {
|
|
68
|
+
const result = await runCommand('npx', ['prettier', '--check', ...chunk], dirPath);
|
|
69
|
+
if (result.exitCode > 1) {
|
|
70
|
+
acc.failed = exitNote('Prettier', result);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
acc.ran = true;
|
|
74
|
+
for (const line of result.stdout.split('\n')) {
|
|
75
|
+
if (!line.trim() || line.startsWith('Checking'))
|
|
76
|
+
continue;
|
|
77
|
+
const rel = line.startsWith('/') ? line.replace(`${dirPath}/`, '') : line;
|
|
78
|
+
unformattedSet.add(rel.trim());
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
acc.passingFiles += Math.max(0, targets.length - unformattedSet.size);
|
|
82
|
+
for (const rel of unformattedSet) {
|
|
83
|
+
acc.findings.push({ severity: 'low', category: 'format', file: rel, line: null, title: 'File not formatted', description: 'Does not match Prettier formatting rules.' });
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
async function fmtPython(dirPath, files, acc) {
|
|
87
|
+
const targets = filesByExt(files, PY_FMT_EXTS);
|
|
88
|
+
if (targets.length === 0) {
|
|
89
|
+
acc.notRun.push('no Python files to format');
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
acc.totalFiles += targets.length;
|
|
93
|
+
let reformatCount = 0;
|
|
94
|
+
const findings = [];
|
|
95
|
+
for (const chunk of chunkFileList(targets)) {
|
|
96
|
+
const result = await runCommand('black', ['--check', '--quiet', ...chunk], dirPath);
|
|
97
|
+
if (result.exitCode > 1) {
|
|
98
|
+
acc.failed = exitNote('Black', result);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
acc.ran = true;
|
|
102
|
+
if (result.exitCode === 0)
|
|
103
|
+
continue;
|
|
104
|
+
const reformatLines = result.stderr.split('\n').filter((l) => l.includes('would reformat'));
|
|
105
|
+
reformatCount += reformatLines.length;
|
|
106
|
+
for (const line of reformatLines) {
|
|
107
|
+
const match = line.match(/would reformat (.+)/);
|
|
108
|
+
if (match)
|
|
109
|
+
findings.push({ severity: 'low', category: 'format', file: match[1].trim(), line: null, title: 'File not formatted', description: 'Does not match Black formatting rules.' });
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
acc.passingFiles += Math.max(0, targets.length - reformatCount);
|
|
113
|
+
acc.findings.push(...findings);
|
|
114
|
+
}
|
|
115
|
+
async function fmtRust(dirPath, files, acc, cargoRoot) {
|
|
116
|
+
const result = await runCommand('cargo', ['fmt', '--check'], dirPath);
|
|
117
|
+
if (result.exitCode > 1) {
|
|
118
|
+
acc.failed = exitNote('rustfmt', result);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
acc.ran = true;
|
|
122
|
+
const rsFiles = files.filter((f) => extname(f.path) === '.rs');
|
|
123
|
+
acc.totalFiles += rsFiles.length;
|
|
124
|
+
if (result.exitCode === 0) {
|
|
125
|
+
acc.passingFiles += rsFiles.length;
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
const diffLines = result.stdout.split('\n').filter((l) => l.startsWith('Diff in'));
|
|
129
|
+
for (const line of diffLines) {
|
|
130
|
+
const match = line.match(/Diff in (.+?) at/);
|
|
131
|
+
if (match)
|
|
132
|
+
acc.findings.push({ severity: 'low', category: 'format', file: normalizeFindingPath(match[1].trim(), dirPath, cargoRoot), line: null, title: 'File not formatted', description: 'Does not match rustfmt formatting rules.' });
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
function notRun(reason) {
|
|
136
|
+
return { score: 0, available: false, issueCount: 0, findings: [], reason };
|
|
137
|
+
}
|
|
138
|
+
function noInstalledTool(category, ecosystems) {
|
|
139
|
+
const rostered = ecosystems.flatMap((eco) => (ECOSYSTEM_TOOLS[eco] || []).filter((spec) => spec.category === category).map((spec) => `${spec.name} (${spec.installCmd})`));
|
|
140
|
+
return rostered.length > 0
|
|
141
|
+
? `no ${category} is installed — ${rostered.join(' or ')}`
|
|
142
|
+
: `no ${category} is rostered for ${ecosystems.join(', ')}`;
|
|
143
|
+
}
|
|
144
|
+
async function analyzeFormatting(dirPath, ecosystems, files, cargoRoot = dirPath) {
|
|
145
|
+
const acc = newFmtAccumulator();
|
|
146
|
+
if (ecosystems.includes('node'))
|
|
147
|
+
await fmtNode(dirPath, files, acc);
|
|
148
|
+
if (ecosystems.includes('python') && !acc.failed)
|
|
149
|
+
await fmtPython(dirPath, files, acc);
|
|
150
|
+
if (ecosystems.includes('rust') && !acc.failed)
|
|
151
|
+
await fmtRust(dirPath, files, acc, cargoRoot);
|
|
152
|
+
if (acc.failed)
|
|
153
|
+
return notRun(acc.failed);
|
|
154
|
+
if (!acc.ran || acc.totalFiles === 0) {
|
|
155
|
+
return notRun(acc.notRun.length > 0 ? acc.notRun.join('; ') : `no formatter ran — none is rostered for ${ecosystems.join(', ')}`);
|
|
156
|
+
}
|
|
157
|
+
const score = Math.round((acc.passingFiles / acc.totalFiles) * 100);
|
|
158
|
+
return { score, available: true, issueCount: acc.findings.length, findings: acc.findings.slice(0, ANALYZER_FINDING_BOUND) };
|
|
159
|
+
}
|
|
160
|
+
const BUILD_FINDING_CAP = 5;
|
|
161
|
+
const TYPESCRIPT_INSTALL_COMMAND = 'npm install -D typescript';
|
|
162
|
+
function tscOutputToFindings(output, dirPath) {
|
|
163
|
+
const findings = [];
|
|
164
|
+
const errorPattern = /^(.+?)\((\d+),\d+\):\s+error\s+TS\d+:\s+(.+)$/gm;
|
|
165
|
+
for (const match of output.matchAll(errorPattern)) {
|
|
166
|
+
if (findings.length >= BUILD_FINDING_CAP)
|
|
167
|
+
break;
|
|
168
|
+
const filePath = match[1].replace(`${dirPath}/`, '').replace(/^\.\//, '');
|
|
169
|
+
findings.push({
|
|
170
|
+
severity: 'critical',
|
|
171
|
+
category: 'build',
|
|
172
|
+
file: filePath,
|
|
173
|
+
line: Number.parseInt(match[2], 10) || null,
|
|
174
|
+
title: `TypeScript build error`,
|
|
175
|
+
description: match[3].trim(),
|
|
176
|
+
suggestion: 'Resolve compile errors before merging — broken builds block all other quality work.',
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
return findings;
|
|
180
|
+
}
|
|
181
|
+
function cargoCheckOutputToFindings(output, dirPath, cargoRoot) {
|
|
182
|
+
const findings = [];
|
|
183
|
+
const errorPattern = /^error(?:\[E\d+\])?:\s+(.+?)$\s+-->\s+([^:\s]+):(\d+):\d+/gm;
|
|
184
|
+
for (const match of output.matchAll(errorPattern)) {
|
|
185
|
+
if (findings.length >= BUILD_FINDING_CAP)
|
|
186
|
+
break;
|
|
187
|
+
findings.push({
|
|
188
|
+
severity: 'critical',
|
|
189
|
+
category: 'build',
|
|
190
|
+
file: normalizeFindingPath(match[2], dirPath, cargoRoot),
|
|
191
|
+
line: Number.parseInt(match[3], 10) || null,
|
|
192
|
+
title: `Rust build error`,
|
|
193
|
+
description: match[1].trim(),
|
|
194
|
+
suggestion: 'Resolve compile errors before merging — broken builds block all other quality work.',
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
return findings;
|
|
198
|
+
}
|
|
199
|
+
async function checkNodeBuild(dirPath, installed) {
|
|
200
|
+
if (installed && !installed.has('typescript')) {
|
|
201
|
+
return { ran: false, reason: `typescript is not installed — ${TYPESCRIPT_INSTALL_COMMAND}` };
|
|
202
|
+
}
|
|
203
|
+
if (!existsSync(`${dirPath}/tsconfig.json`)) {
|
|
204
|
+
return { ran: false, reason: 'tsc --noEmit needs a tsconfig.json at the root of the scanned directory and there is none — a config in a subdirectory is never picked up' };
|
|
205
|
+
}
|
|
206
|
+
const result = await runCommand('npx', ['tsc', '--noEmit', '--pretty', 'false'], dirPath);
|
|
207
|
+
if (result.exitCode === 0)
|
|
208
|
+
return { ran: true, findings: [] };
|
|
209
|
+
const findings = tscOutputToFindings(`${result.stdout}\n${result.stderr}`, dirPath);
|
|
210
|
+
if (findings.length === 0) {
|
|
211
|
+
const detail = failureDetail(result);
|
|
212
|
+
return { ran: false, reason: `tsc --noEmit exited ${result.exitCode} without a parseable error${detail ? `: ${detail}` : ''}` };
|
|
213
|
+
}
|
|
214
|
+
return { ran: true, findings };
|
|
215
|
+
}
|
|
216
|
+
async function checkRustBuild(dirPath, cargoRoot) {
|
|
217
|
+
const result = await runCommand('cargo', ['check', '--message-format=human'], dirPath);
|
|
218
|
+
if (result.exitCode === 0)
|
|
219
|
+
return { ran: true, findings: [] };
|
|
220
|
+
const findings = cargoCheckOutputToFindings(`${result.stdout}\n${result.stderr}`, dirPath, cargoRoot);
|
|
221
|
+
if (findings.length === 0) {
|
|
222
|
+
const detail = failureDetail(result);
|
|
223
|
+
return { ran: false, reason: `cargo check exited ${result.exitCode} without a parseable error${detail ? `: ${detail}` : ''}` };
|
|
224
|
+
}
|
|
225
|
+
return { ran: true, findings };
|
|
226
|
+
}
|
|
227
|
+
async function analyzeBuildErrors(dirPath, ecosystems, installed, cargoRoot = dirPath) {
|
|
228
|
+
const findings = [];
|
|
229
|
+
const skipped = [];
|
|
230
|
+
let ran = false;
|
|
231
|
+
if (ecosystems.includes('node')) {
|
|
232
|
+
const check = await checkNodeBuild(dirPath, installed);
|
|
233
|
+
if (check.ran) {
|
|
234
|
+
ran = true;
|
|
235
|
+
findings.push(...check.findings);
|
|
236
|
+
}
|
|
237
|
+
else {
|
|
238
|
+
skipped.push(check.reason);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
if (ecosystems.includes('rust')) {
|
|
242
|
+
const check = await checkRustBuild(dirPath, cargoRoot);
|
|
243
|
+
if (check.ran) {
|
|
244
|
+
ran = true;
|
|
245
|
+
findings.push(...check.findings);
|
|
246
|
+
}
|
|
247
|
+
else {
|
|
248
|
+
skipped.push(check.reason);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
if (!ran) {
|
|
252
|
+
return {
|
|
253
|
+
findings: [],
|
|
254
|
+
available: false,
|
|
255
|
+
reason: skipped.length > 0 ? skipped.join('; ') : `no build check exists for ${ecosystems.join(', ')} — tsc --noEmit runs for node and cargo check for rust`,
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
return { findings, available: true };
|
|
259
|
+
}
|
|
260
|
+
const TOP_LEVEL_EXPORT_PATTERN = /^export\s+(?:default\s+)?(?:async\s+)?(?:function|class|const|let|var|interface|type|enum)\s+(\w+)/gm;
|
|
261
|
+
const TOP_LEVEL_DECL_PATTERN = /^(?:export\s+)?(?:async\s+)?(?:function|class)\s+(\w+)/gm;
|
|
262
|
+
const PY_TOP_LEVEL_PATTERN = /^(?:def|class)\s+(\w+)/gm;
|
|
263
|
+
const IMPORT_PATTERN = /^(?:import\s+.+from\s+['"]([^'"]+)['"]|from\s+([^\s]+)\s+import|import\s+([^\s]+))/gm;
|
|
264
|
+
const SECTION_DIVIDER_PATTERN = /^\s*(?:\/\/|#)\s*={3,}|^\s*(?:\/\/|#)\s*-{3,}/gm;
|
|
265
|
+
function distinctIdentifierPrefixes(names) {
|
|
266
|
+
if (names.length === 0)
|
|
267
|
+
return 0;
|
|
268
|
+
const prefixes = new Set();
|
|
269
|
+
for (const name of names) {
|
|
270
|
+
const first = name.replace(/[A-Z][a-z]+|_+/g, (m, _o, _s) => `${m.replace(/_/g, '')}`).split('').filter(Boolean)[0] ?? name;
|
|
271
|
+
const lowered = first.toLowerCase();
|
|
272
|
+
if (lowered.length >= 2)
|
|
273
|
+
prefixes.add(lowered);
|
|
274
|
+
}
|
|
275
|
+
return prefixes.size;
|
|
276
|
+
}
|
|
277
|
+
function importRoot(spec) {
|
|
278
|
+
const trimmed = spec.replace(/^['"]|['"]$/g, '').trim();
|
|
279
|
+
if (!trimmed)
|
|
280
|
+
return '';
|
|
281
|
+
if (trimmed.startsWith('.'))
|
|
282
|
+
return 'relative';
|
|
283
|
+
return trimmed.split('/')[0].replace(/^@/, '');
|
|
284
|
+
}
|
|
285
|
+
function jsDeclNames(content) {
|
|
286
|
+
const exports = [];
|
|
287
|
+
const decls = [];
|
|
288
|
+
for (const match of content.matchAll(TOP_LEVEL_EXPORT_PATTERN))
|
|
289
|
+
exports.push(match[1]);
|
|
290
|
+
for (const match of content.matchAll(TOP_LEVEL_DECL_PATTERN))
|
|
291
|
+
decls.push(match[1]);
|
|
292
|
+
return { exports, decls };
|
|
293
|
+
}
|
|
294
|
+
function pyDeclNames(content) {
|
|
295
|
+
const exports = [];
|
|
296
|
+
const decls = [];
|
|
297
|
+
for (const match of content.matchAll(PY_TOP_LEVEL_PATTERN)) {
|
|
298
|
+
decls.push(match[1]);
|
|
299
|
+
if (!match[1].startsWith('_'))
|
|
300
|
+
exports.push(match[1]);
|
|
301
|
+
}
|
|
302
|
+
return { exports, decls };
|
|
303
|
+
}
|
|
304
|
+
function importRootCount(content) {
|
|
305
|
+
const roots = new Set();
|
|
306
|
+
for (const match of content.matchAll(IMPORT_PATTERN)) {
|
|
307
|
+
const spec = match[1] ?? match[2] ?? match[3] ?? '';
|
|
308
|
+
const root = importRoot(spec);
|
|
309
|
+
if (root)
|
|
310
|
+
roots.add(root);
|
|
311
|
+
}
|
|
312
|
+
return roots.size;
|
|
313
|
+
}
|
|
314
|
+
function collectCohesionSignals(file) {
|
|
315
|
+
const ext = extname(file.path).toLowerCase();
|
|
316
|
+
const isJs = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs'].includes(ext);
|
|
317
|
+
const isPy = ['.py', '.pyi'].includes(ext);
|
|
318
|
+
const { exports, decls } = isJs
|
|
319
|
+
? jsDeclNames(file.content)
|
|
320
|
+
: isPy
|
|
321
|
+
? pyDeclNames(file.content)
|
|
322
|
+
: { exports: [], decls: [] };
|
|
323
|
+
const allNames = exports.length > 0 ? exports : decls;
|
|
324
|
+
return {
|
|
325
|
+
exports: exports.length,
|
|
326
|
+
decls: decls.length,
|
|
327
|
+
prefixes: distinctIdentifierPrefixes(allNames),
|
|
328
|
+
importRoots: importRootCount(file.content),
|
|
329
|
+
dividers: (file.content.match(SECTION_DIVIDER_PATTERN) || []).length,
|
|
330
|
+
isJs,
|
|
331
|
+
isPy,
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
function computeMixedConcernsScore(file) {
|
|
335
|
+
const sig = collectCohesionSignals(file);
|
|
336
|
+
if (!sig.isJs && !sig.isPy)
|
|
337
|
+
return 0;
|
|
338
|
+
const exportComponent = sig.exports <= 2 ? 0 : Math.min(1, (sig.exports - 2) / 12);
|
|
339
|
+
const prefixComponent = sig.prefixes <= 1 ? 0 : Math.min(1, (sig.prefixes - 1) / 6);
|
|
340
|
+
const importComponent = sig.importRoots <= 4 ? 0 : Math.min(1, (sig.importRoots - 4) / 12);
|
|
341
|
+
const dividerComponent = sig.dividers <= 2 ? 0 : Math.min(1, (sig.dividers - 2) / 6);
|
|
342
|
+
return Math.min(1, 0.30 * prefixComponent +
|
|
343
|
+
0.30 * exportComponent +
|
|
344
|
+
0.25 * importComponent +
|
|
345
|
+
0.15 * dividerComponent);
|
|
346
|
+
}
|
|
347
|
+
function severityFromCohesion(mixed, lines) {
|
|
348
|
+
const isAbsurd = lines > FILE_LENGTH_THRESHOLD * 5;
|
|
349
|
+
if (mixed <= 0.30 && !isAbsurd)
|
|
350
|
+
return null;
|
|
351
|
+
if (mixed <= 0.55)
|
|
352
|
+
return 'low';
|
|
353
|
+
if (mixed <= 0.75)
|
|
354
|
+
return 'medium';
|
|
355
|
+
return 'high';
|
|
356
|
+
}
|
|
357
|
+
function analyzeFileLength(files) {
|
|
358
|
+
if (files.length === 0)
|
|
359
|
+
return { score: 100, findings: [], issueCount: 0 };
|
|
360
|
+
const findings = [];
|
|
361
|
+
let totalScore = 0;
|
|
362
|
+
let scoredFiles = 0;
|
|
363
|
+
for (const file of files) {
|
|
364
|
+
if (isTestFile(file.relativePath))
|
|
365
|
+
continue;
|
|
366
|
+
const ratio = Math.max(1, file.lines / FILE_LENGTH_THRESHOLD);
|
|
367
|
+
const fileScore = 100 / ratio ** 1.5;
|
|
368
|
+
totalScore += fileScore;
|
|
369
|
+
scoredFiles++;
|
|
370
|
+
if (file.lines > FILE_LENGTH_THRESHOLD) {
|
|
371
|
+
const mixedScore = computeMixedConcernsScore(file);
|
|
372
|
+
const severity = severityFromCohesion(mixedScore, file.lines);
|
|
373
|
+
if (!severity)
|
|
374
|
+
continue;
|
|
375
|
+
const cohesionPct = Math.round((1 - mixedScore) * 100);
|
|
376
|
+
findings.push({
|
|
377
|
+
severity,
|
|
378
|
+
category: 'file-length',
|
|
379
|
+
file: file.relativePath,
|
|
380
|
+
line: null,
|
|
381
|
+
title: `File has ${file.lines} lines (threshold: ${FILE_LENGTH_THRESHOLD}, cohesion: ${cohesionPct}%)`,
|
|
382
|
+
description: `Exceeds the ${FILE_LENGTH_THRESHOLD}-line threshold by ${file.lines - FILE_LENGTH_THRESHOLD} lines. ` +
|
|
383
|
+
`Mixed-concerns score is ${roundOne(mixedScore)} (0 = focused, 1 = many concerns); ` +
|
|
384
|
+
`severity reflects how mixed the file's responsibilities appear. ` +
|
|
385
|
+
(mixedScore > 0.55
|
|
386
|
+
? 'Consider splitting unrelated exports into separate modules.'
|
|
387
|
+
: 'The file is long but reasonably focused — split only if a clear seam exists.'),
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
if (scoredFiles === 0)
|
|
392
|
+
return { score: 100, findings: [], issueCount: 0 };
|
|
393
|
+
const score = Math.round(totalScore / scoredFiles);
|
|
394
|
+
return { score: Math.min(100, score), findings: findings.slice(0, ANALYZER_FINDING_BOUND), issueCount: findings.length };
|
|
395
|
+
}
|
|
396
|
+
function roundOne(n) {
|
|
397
|
+
return Math.round(n * 10) / 10;
|
|
398
|
+
}
|
|
399
|
+
function computeStructuralScope(files, duplicationRan) {
|
|
400
|
+
const heuristic = new Set(STRUCTURAL_EXTENSIONS);
|
|
401
|
+
const present = new Set(files.map((f) => extname(f.path).toLowerCase()));
|
|
402
|
+
const extensions = [...present].filter((ext) => duplicationRan || heuristic.has(ext)).sort();
|
|
403
|
+
const inScope = new Set(extensions);
|
|
404
|
+
let count = 0;
|
|
405
|
+
for (const f of files) {
|
|
406
|
+
if (!inScope.has(extname(f.path).toLowerCase()))
|
|
407
|
+
continue;
|
|
408
|
+
const role = classifyFileRole(f.relativePath);
|
|
409
|
+
if (role === 'test' || role === 'generated')
|
|
410
|
+
continue;
|
|
411
|
+
count++;
|
|
412
|
+
}
|
|
413
|
+
return { files: count, extensions };
|
|
414
|
+
}
|
|
415
|
+
const LINT_LIST_CAP = 100;
|
|
416
|
+
const CATEGORY_LIST_CAP = 50;
|
|
417
|
+
const SEVERITY_RANK = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
418
|
+
function listingRank(f) {
|
|
419
|
+
return SEVERITY_RANK[f.severity] ?? SEVERITY_RANK.low + 1;
|
|
420
|
+
}
|
|
421
|
+
function dismissedRank(f) {
|
|
422
|
+
return f.dismissal === undefined ? 0 : 1;
|
|
423
|
+
}
|
|
424
|
+
function introducedRank(f) {
|
|
425
|
+
return f.introduced === true ? 0 : 1;
|
|
426
|
+
}
|
|
427
|
+
function inListingOrder(findings) {
|
|
428
|
+
return [...findings].sort((a, b) => dismissedRank(a) - dismissedRank(b) || introducedRank(a) - introducedRank(b) || listingRank(a) - listingRank(b));
|
|
429
|
+
}
|
|
430
|
+
export function applyListCap(findings, cap) {
|
|
431
|
+
return inListingOrder(findings).slice(0, cap);
|
|
432
|
+
}
|
|
433
|
+
export function isActiveFinding(f) {
|
|
434
|
+
return f.dismissal === undefined && f.status !== 'likely-fp';
|
|
435
|
+
}
|
|
436
|
+
export function indexDismissals(path, suppressions) {
|
|
437
|
+
const index = new Map();
|
|
438
|
+
for (const entry of suppressions) {
|
|
439
|
+
if (entry.path !== path)
|
|
440
|
+
continue;
|
|
441
|
+
index.set(entry.fingerprint, { reason: entry.reason, dismissedAt: entry.dismissedAt });
|
|
442
|
+
}
|
|
443
|
+
return index;
|
|
444
|
+
}
|
|
445
|
+
export function stampDismissals(findings, dismissals) {
|
|
446
|
+
for (const f of findings) {
|
|
447
|
+
const dismissal = f.fingerprint === undefined ? undefined : dismissals.get(f.fingerprint);
|
|
448
|
+
if (dismissal)
|
|
449
|
+
f.dismissal = { ...dismissal };
|
|
450
|
+
else if (f.dismissal !== undefined)
|
|
451
|
+
delete f.dismissal;
|
|
452
|
+
}
|
|
453
|
+
return findings;
|
|
454
|
+
}
|
|
455
|
+
export function applyDismissals(results, dismissals) {
|
|
456
|
+
const copy = (findings) => (findings ?? []).map((f) => ({ ...f }));
|
|
457
|
+
return recomputeGrade({
|
|
458
|
+
...results,
|
|
459
|
+
categories: results.categories ?? [],
|
|
460
|
+
findings: stampDismissals(copy(results.findings), dismissals),
|
|
461
|
+
codeReview: stampDismissals(copy(results.codeReview), dismissals),
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
export function stampIntroduced(findings, baseline) {
|
|
465
|
+
for (const f of findings) {
|
|
466
|
+
f.introduced = !(f.fingerprint !== undefined && baseline.fingerprints.has(f.fingerprint));
|
|
467
|
+
}
|
|
468
|
+
return findings;
|
|
469
|
+
}
|
|
470
|
+
function computeGrade(score) {
|
|
471
|
+
return gradeFromScore(score);
|
|
472
|
+
}
|
|
473
|
+
const SEVERITY_WEIGHT = {
|
|
474
|
+
critical: 10,
|
|
475
|
+
high: 5,
|
|
476
|
+
medium: 2,
|
|
477
|
+
low: 0.5,
|
|
478
|
+
};
|
|
479
|
+
const CATEGORY_MULTIPLIER = {
|
|
480
|
+
security: 2.0,
|
|
481
|
+
bugs: 1.5,
|
|
482
|
+
architecture: 1.2,
|
|
483
|
+
logic: 1.2,
|
|
484
|
+
performance: 1.0,
|
|
485
|
+
oop: 0.8,
|
|
486
|
+
maintainability: 0.8,
|
|
487
|
+
complexity: 0.7,
|
|
488
|
+
lint: 0.5,
|
|
489
|
+
linting: 0.5,
|
|
490
|
+
format: 0.3,
|
|
491
|
+
'file-length': 0.3,
|
|
492
|
+
'function-length': 0.3,
|
|
493
|
+
};
|
|
494
|
+
const OVERALL_DECAY = 0.09;
|
|
495
|
+
const CATEGORY_DECAY = 0.20;
|
|
496
|
+
function findingPenalty(f) {
|
|
497
|
+
return (SEVERITY_WEIGHT[f.severity] ?? 2) * (CATEGORY_MULTIPLIER[f.category] ?? 1.0);
|
|
498
|
+
}
|
|
499
|
+
export function computeFormulaScore(allFindings, totalLines, unlistedCount = 0) {
|
|
500
|
+
const kloc = Math.max(totalLines / 1000, 1.0);
|
|
501
|
+
const issueDensity = Math.round(((allFindings.length + unlistedCount) / kloc) * 100) / 100;
|
|
502
|
+
if (allFindings.length === 0) {
|
|
503
|
+
return {
|
|
504
|
+
score: 100,
|
|
505
|
+
breakdown: { penaltyDensity: 0, totalPenalty: 0, issueDensity, kloc, categoryPenalties: [] },
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
const byCategory = new Map();
|
|
509
|
+
let totalPenalty = 0;
|
|
510
|
+
for (const f of allFindings) {
|
|
511
|
+
const p = findingPenalty(f);
|
|
512
|
+
totalPenalty += p;
|
|
513
|
+
const existing = byCategory.get(f.category);
|
|
514
|
+
if (existing) {
|
|
515
|
+
existing.penalty += p;
|
|
516
|
+
existing.count++;
|
|
517
|
+
}
|
|
518
|
+
else {
|
|
519
|
+
byCategory.set(f.category, { penalty: p, count: 1 });
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
const penaltyDensity = totalPenalty / kloc;
|
|
523
|
+
const score = Math.round(100 * Math.exp(-OVERALL_DECAY * penaltyDensity));
|
|
524
|
+
const categoryPenalties = [];
|
|
525
|
+
for (const [cat, data] of byCategory) {
|
|
526
|
+
const catDensity = data.penalty / kloc;
|
|
527
|
+
const catScore = Math.round(100 * Math.exp(-CATEGORY_DECAY * catDensity));
|
|
528
|
+
categoryPenalties.push({
|
|
529
|
+
category: cat,
|
|
530
|
+
score: catScore,
|
|
531
|
+
grade: computeGrade(catScore),
|
|
532
|
+
penalty: Math.round(data.penalty * 10) / 10,
|
|
533
|
+
findingCount: data.count,
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
categoryPenalties.sort((a, b) => a.score - b.score);
|
|
537
|
+
return {
|
|
538
|
+
score,
|
|
539
|
+
breakdown: {
|
|
540
|
+
penaltyDensity: Math.round(penaltyDensity * 100) / 100,
|
|
541
|
+
totalPenalty: Math.round(totalPenalty * 10) / 10,
|
|
542
|
+
issueDensity,
|
|
543
|
+
kloc: Math.round(kloc * 10) / 10,
|
|
544
|
+
categoryPenalties,
|
|
545
|
+
},
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
function computeGradeFields(allFindings, totalLines, forceNA, unlistedCount) {
|
|
549
|
+
const counted = allFindings.filter(findingCountsTowardGrade);
|
|
550
|
+
const { breakdown } = computeFormulaScore(counted, totalLines, unlistedCount);
|
|
551
|
+
const rating = computeQualityRating(counted, totalLines, { forceNA });
|
|
552
|
+
const prodGating = counted.filter((f) => f.category === 'build' || classifyFileRole(f.file) === 'production');
|
|
553
|
+
const prodRating = computeQualityRating(prodGating, totalLines, { forceNA });
|
|
554
|
+
const readiness = computeReadiness(prodRating.dimensions, prodGating);
|
|
555
|
+
return {
|
|
556
|
+
overall: rating.overall.score,
|
|
557
|
+
grade: rating.overall.grade,
|
|
558
|
+
scoreBreakdown: breakdown,
|
|
559
|
+
dimensions: rating.dimensions,
|
|
560
|
+
qualityGate: rating.qualityGate,
|
|
561
|
+
gradeRationale: rating.gradeRationale,
|
|
562
|
+
readiness,
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
function unlistedFindingCount(categories, findings) {
|
|
566
|
+
const counted = categories.reduce((sum, category) => sum + (category.issueCount ?? 0), 0);
|
|
567
|
+
return Math.max(0, counted - findings.length);
|
|
568
|
+
}
|
|
569
|
+
function toolBackedRow(name, result) {
|
|
570
|
+
return {
|
|
571
|
+
name,
|
|
572
|
+
score: result.score,
|
|
573
|
+
available: result.available,
|
|
574
|
+
issueCount: result.issueCount,
|
|
575
|
+
...(result.reason === undefined ? {} : { reason: result.reason }),
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
function forceNAFromCategories(categories) {
|
|
579
|
+
const forceNA = new Set();
|
|
580
|
+
const linting = categories.find((c) => c.name === 'Linting');
|
|
581
|
+
const formatting = categories.find((c) => c.name === 'Formatting');
|
|
582
|
+
const complexity = categories.find((c) => c.name === 'Complexity');
|
|
583
|
+
if (!linting?.available && !formatting?.available && !complexity?.available) {
|
|
584
|
+
forceNA.add('maintainability');
|
|
585
|
+
}
|
|
586
|
+
return forceNA;
|
|
587
|
+
}
|
|
588
|
+
export function summarizeTriage(findings) {
|
|
589
|
+
let confirmed = 0;
|
|
590
|
+
let likelyFalsePositive = 0;
|
|
591
|
+
let uncertain = 0;
|
|
592
|
+
for (const f of findings) {
|
|
593
|
+
if (f.status === 'likely-fp')
|
|
594
|
+
likelyFalsePositive++;
|
|
595
|
+
else if (f.status === 'uncertain')
|
|
596
|
+
uncertain++;
|
|
597
|
+
else
|
|
598
|
+
confirmed++;
|
|
599
|
+
}
|
|
600
|
+
return { total: findings.length, confirmed, likelyFalsePositive, uncertain };
|
|
601
|
+
}
|
|
602
|
+
function stampStatuses(findings) {
|
|
603
|
+
for (const f of findings) {
|
|
604
|
+
if (f.status)
|
|
605
|
+
continue;
|
|
606
|
+
f.status = 'confirmed';
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
async function triageScanFindings(dirPath, allFindings, onProgress, signal, models) {
|
|
610
|
+
const candidates = allFindings.filter((f) => STRUCTURAL_CATEGORIES.has(f.category) && f.status === 'confirmed' && f.dismissal === undefined);
|
|
611
|
+
if (candidates.length === 0)
|
|
612
|
+
return allFindings;
|
|
613
|
+
onProgress?.({ step: 'Triaging findings', current: TRIAGE_STEP, total: TOTAL_STEPS });
|
|
614
|
+
const triageSend = (message) => onProgress?.({ step: message, current: TRIAGE_STEP, total: TOTAL_STEPS });
|
|
615
|
+
try {
|
|
616
|
+
const selection = models ?? backgroundTriageModels();
|
|
617
|
+
const triageResult = await runTriagePass(dirPath, candidates, triageSend, signal, selection.triageModel, selection.engine, selection.effortLevel, selection.fastMode);
|
|
618
|
+
if (!triageResult.ran)
|
|
619
|
+
return allFindings;
|
|
620
|
+
const verdictByCandidate = new Map();
|
|
621
|
+
for (let i = 0; i < candidates.length; i++) {
|
|
622
|
+
verdictByCandidate.set(candidates[i], triageResult.findings[i]);
|
|
623
|
+
}
|
|
624
|
+
return allFindings.map((f) => verdictByCandidate.get(f) ?? f);
|
|
625
|
+
}
|
|
626
|
+
catch {
|
|
627
|
+
return allFindings;
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
export class QualityScanAbortedError extends Error {
|
|
631
|
+
constructor() {
|
|
632
|
+
super('Quality scan aborted');
|
|
633
|
+
this.name = 'QualityScanAbortedError';
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
function checkAborted(signal) {
|
|
637
|
+
if (signal?.aborted)
|
|
638
|
+
throw new QualityScanAbortedError();
|
|
639
|
+
}
|
|
640
|
+
export function runQualityScan(dirPath, onProgressOrOptions, installedToolNames, signal, models, baseline, dismissals) {
|
|
641
|
+
const options = typeof onProgressOrOptions === 'object'
|
|
642
|
+
? onProgressOrOptions
|
|
643
|
+
: { onProgress: onProgressOrOptions, installedToolNames, signal, models, baseline, dismissals };
|
|
644
|
+
return runScan(dirPath, options);
|
|
645
|
+
}
|
|
646
|
+
async function runScan(dirPath, options) {
|
|
647
|
+
const { onProgress, installedToolNames, signal, models, baseline, dismissals, toolOutputDir } = options;
|
|
648
|
+
const ecosystems = detectEcosystem(dirPath);
|
|
649
|
+
const cargoRoot = ecosystems.includes('rust') ? await cargoWorkspaceRoot(dirPath) : dirPath;
|
|
650
|
+
const installedSet = installedToolNames ? new Set(installedToolNames) : null;
|
|
651
|
+
const progress = (step, current) => {
|
|
652
|
+
onProgress?.({ step, current, total: TOTAL_STEPS });
|
|
653
|
+
};
|
|
654
|
+
checkAborted(signal);
|
|
655
|
+
progress('Collecting source files', 1);
|
|
656
|
+
const files = options.files ? readSourceFiles(dirPath, options.files) : await collectSourceFiles(dirPath, dirPath);
|
|
657
|
+
checkAborted(signal);
|
|
658
|
+
progress('Running linters', 2);
|
|
659
|
+
const lintResult = !installedSet || hasInstalledToolInCategory(installedSet, ecosystems, 'linter')
|
|
660
|
+
? await analyzeLinting(dirPath, ecosystems, files, installedToolNames, cargoRoot)
|
|
661
|
+
: notRun(noInstalledTool('linter', ecosystems));
|
|
662
|
+
checkAborted(signal);
|
|
663
|
+
progress('Checking formatting', 3);
|
|
664
|
+
const fmtResult = !installedSet || hasInstalledToolInCategory(installedSet, ecosystems, 'formatter')
|
|
665
|
+
? await analyzeFormatting(dirPath, ecosystems, files, cargoRoot)
|
|
666
|
+
: notRun(noInstalledTool('formatter', ecosystems));
|
|
667
|
+
checkAborted(signal);
|
|
668
|
+
progress('Checking build', 4);
|
|
669
|
+
const buildResult = await analyzeBuildErrors(dirPath, ecosystems, installedSet, cargoRoot);
|
|
670
|
+
checkAborted(signal);
|
|
671
|
+
progress('Analyzing complexity', 5);
|
|
672
|
+
const complexityResult = await analyzeComplexity(dirPath, ecosystems, files, installedToolNames);
|
|
673
|
+
checkAborted(signal);
|
|
674
|
+
progress('Checking file lengths', 6);
|
|
675
|
+
const fileLengthResult = analyzeFileLength(files);
|
|
676
|
+
checkAborted(signal);
|
|
677
|
+
progress('Checking function lengths', 7);
|
|
678
|
+
const funcLengthResult = analyzeFunctionLength(files);
|
|
679
|
+
checkAborted(signal);
|
|
680
|
+
progress('Detecting duplication', 8);
|
|
681
|
+
const duplicationResult = await analyzeDuplication(dirPath, ecosystems, files, installedSet, toolOutputDir);
|
|
682
|
+
checkAborted(signal);
|
|
683
|
+
progress('Scanning for secrets', 9);
|
|
684
|
+
const secretsResult = await analyzeSecrets(dirPath, toolOutputDir);
|
|
685
|
+
checkAborted(signal);
|
|
686
|
+
progress('Auditing dependencies', 10);
|
|
687
|
+
const dependenciesResult = await analyzeDependencies(dirPath, ecosystems, installedSet, cargoRoot, options.files !== undefined);
|
|
688
|
+
const produced = [
|
|
689
|
+
...buildResult.findings,
|
|
690
|
+
...lintResult.findings,
|
|
691
|
+
...fmtResult.findings,
|
|
692
|
+
...complexityResult.findings,
|
|
693
|
+
...fileLengthResult.findings,
|
|
694
|
+
...funcLengthResult.findings,
|
|
695
|
+
...duplicationResult.findings,
|
|
696
|
+
...secretsResult.findings,
|
|
697
|
+
...dependenciesResult.findings,
|
|
698
|
+
];
|
|
699
|
+
stampStatuses(produced);
|
|
700
|
+
stampFingerprints(produced, { dirPath, contentOf: collectedContentReader(files) });
|
|
701
|
+
const fingerprints = produced.flatMap((f) => (f.fingerprint ? [f.fingerprint] : []));
|
|
702
|
+
if (dismissals)
|
|
703
|
+
stampDismissals(produced, dismissals);
|
|
704
|
+
if (baseline)
|
|
705
|
+
stampIntroduced(produced, baseline);
|
|
706
|
+
const allFindings = [
|
|
707
|
+
...buildResult.findings,
|
|
708
|
+
...applyListCap(lintResult.findings, LINT_LIST_CAP),
|
|
709
|
+
...applyListCap(fmtResult.findings, CATEGORY_LIST_CAP),
|
|
710
|
+
...applyListCap(complexityResult.findings, CATEGORY_LIST_CAP),
|
|
711
|
+
...applyListCap(fileLengthResult.findings, CATEGORY_LIST_CAP),
|
|
712
|
+
...applyListCap(funcLengthResult.findings, CATEGORY_LIST_CAP),
|
|
713
|
+
...applyListCap(duplicationResult.findings, CATEGORY_LIST_CAP),
|
|
714
|
+
...applyListCap(secretsResult.findings, CATEGORY_LIST_CAP),
|
|
715
|
+
...applyListCap(dependenciesResult.findings, CATEGORY_LIST_CAP),
|
|
716
|
+
];
|
|
717
|
+
checkAborted(signal);
|
|
718
|
+
const triagedFindings = await triageScanFindings(dirPath, allFindings, onProgress, signal, models);
|
|
719
|
+
checkAborted(signal);
|
|
720
|
+
progress('Computing scores', SCORING_STEP);
|
|
721
|
+
const totalLines = files.reduce((sum, f) => sum + f.lines, 0);
|
|
722
|
+
const buildScore = buildResult.available && buildResult.findings.length === 0 ? 100 : 0;
|
|
723
|
+
const categories = [
|
|
724
|
+
toolBackedRow('Build', { score: buildScore, available: buildResult.available, issueCount: buildResult.findings.length, reason: buildResult.reason }),
|
|
725
|
+
toolBackedRow('Linting', lintResult),
|
|
726
|
+
toolBackedRow('Formatting', fmtResult),
|
|
727
|
+
{ name: 'Complexity', score: complexityResult.score, available: complexityResult.available, issueCount: complexityResult.issueCount },
|
|
728
|
+
{ name: 'File Length', score: fileLengthResult.score, available: true, issueCount: fileLengthResult.issueCount },
|
|
729
|
+
{ name: 'Function Length', score: funcLengthResult.score, available: true, issueCount: funcLengthResult.issueCount },
|
|
730
|
+
toolBackedRow('Duplication', duplicationResult),
|
|
731
|
+
toolBackedRow('Secrets', secretsResult),
|
|
732
|
+
toolBackedRow('Dependencies', dependenciesResult),
|
|
733
|
+
];
|
|
734
|
+
const forceNA = forceNAFromCategories(categories);
|
|
735
|
+
const gradeFields = computeGradeFields(triagedFindings, totalLines, forceNA, unlistedFindingCount(categories, triagedFindings));
|
|
736
|
+
return {
|
|
737
|
+
overall: gradeFields.overall,
|
|
738
|
+
grade: gradeFields.grade,
|
|
739
|
+
categories,
|
|
740
|
+
findings: inListingOrder(triagedFindings),
|
|
741
|
+
fingerprints,
|
|
742
|
+
...(baseline ? { baselineAt: baseline.timestamp } : {}),
|
|
743
|
+
codeReview: [],
|
|
744
|
+
analyzedFiles: files.length,
|
|
745
|
+
structuralScope: computeStructuralScope(files, duplicationResult.available),
|
|
746
|
+
totalLines,
|
|
747
|
+
timestamp: new Date().toISOString(),
|
|
748
|
+
ecosystem: ecosystems,
|
|
749
|
+
scoreBreakdown: gradeFields.scoreBreakdown,
|
|
750
|
+
dimensions: gradeFields.dimensions,
|
|
751
|
+
qualityGate: gradeFields.qualityGate,
|
|
752
|
+
gradeRationale: gradeFields.gradeRationale,
|
|
753
|
+
readiness: gradeFields.readiness,
|
|
754
|
+
triage: summarizeTriage(triagedFindings),
|
|
755
|
+
};
|
|
756
|
+
}
|
|
757
|
+
export function recomputeGrade(results) {
|
|
758
|
+
const allFindings = [...results.findings, ...results.codeReview];
|
|
759
|
+
const forceNA = forceNAFromCategories(results.categories);
|
|
760
|
+
const gradeFields = computeGradeFields(allFindings, results.totalLines, forceNA, unlistedFindingCount(results.categories, results.findings));
|
|
761
|
+
return {
|
|
762
|
+
...results,
|
|
763
|
+
overall: gradeFields.overall,
|
|
764
|
+
grade: gradeFields.grade,
|
|
765
|
+
scoreBreakdown: gradeFields.scoreBreakdown,
|
|
766
|
+
dimensions: gradeFields.dimensions,
|
|
767
|
+
qualityGate: gradeFields.qualityGate,
|
|
768
|
+
gradeRationale: gradeFields.gradeRationale,
|
|
769
|
+
readiness: gradeFields.readiness,
|
|
770
|
+
triage: summarizeTriage(results.findings),
|
|
771
|
+
};
|
|
772
|
+
}
|
|
773
|
+
export function recomputeWithAiReview(results, aiFindings) {
|
|
774
|
+
return recomputeGrade({ ...results, codeReview: [...results.codeReview, ...aiFindings] });
|
|
775
|
+
}
|