@nxuss/lemma 1.18.0 → 1.18.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/mcp/cognitiveMap.d.ts +27 -0
- package/dist/cjs/mcp/cognitiveMap.d.ts.map +1 -0
- package/dist/cjs/mcp/cognitiveMap.js +47 -0
- package/dist/cjs/mcp/cognitiveMap.js.map +1 -0
- package/dist/cjs/mcp/toolTypes.d.ts +39 -0
- package/dist/cjs/mcp/toolTypes.d.ts.map +1 -0
- package/dist/cjs/mcp/toolTypes.js +8 -0
- package/dist/cjs/mcp/toolTypes.js.map +1 -0
- package/dist/cjs/mcp/tools/analysis.d.ts +11 -0
- package/dist/cjs/mcp/tools/analysis.d.ts.map +1 -0
- package/dist/cjs/mcp/tools/analysis.js +933 -0
- package/dist/cjs/mcp/tools/analysis.js.map +1 -0
- package/dist/cjs/mcp/tools/codenav.d.ts +9 -0
- package/dist/cjs/mcp/tools/codenav.d.ts.map +1 -0
- package/dist/cjs/mcp/tools/codenav.js +524 -0
- package/dist/cjs/mcp/tools/codenav.js.map +1 -0
- package/dist/cjs/mcp/tools/context-tools.d.ts +20 -0
- package/dist/cjs/mcp/tools/context-tools.d.ts.map +1 -0
- package/dist/cjs/mcp/tools/context-tools.js +543 -0
- package/dist/cjs/mcp/tools/context-tools.js.map +1 -0
- package/dist/cjs/mcp/tools/depgraph.d.ts +8 -0
- package/dist/cjs/mcp/tools/depgraph.d.ts.map +1 -0
- package/dist/cjs/mcp/tools/depgraph.js +321 -0
- package/dist/cjs/mcp/tools/depgraph.js.map +1 -0
- package/dist/cjs/mcp/tools/dev-utils.d.ts +11 -0
- package/dist/cjs/mcp/tools/dev-utils.d.ts.map +1 -0
- package/dist/cjs/mcp/tools/dev-utils.js +519 -0
- package/dist/cjs/mcp/tools/dev-utils.js.map +1 -0
- package/dist/cjs/mcp/tools/devtools.d.ts +9 -0
- package/dist/cjs/mcp/tools/devtools.d.ts.map +1 -0
- package/dist/cjs/mcp/tools/devtools.js +445 -0
- package/dist/cjs/mcp/tools/devtools.js.map +1 -0
- package/dist/cjs/mcp/tools/memory.d.ts +10 -0
- package/dist/cjs/mcp/tools/memory.d.ts.map +1 -0
- package/dist/cjs/mcp/tools/memory.js +539 -0
- package/dist/cjs/mcp/tools/memory.js.map +1 -0
- package/dist/cjs/mcp/tools/misc.d.ts +10 -0
- package/dist/cjs/mcp/tools/misc.d.ts.map +1 -0
- package/dist/cjs/mcp/tools/misc.js +423 -0
- package/dist/cjs/mcp/tools/misc.js.map +1 -0
- package/dist/cjs/mcp/tools/refactor.d.ts +8 -0
- package/dist/cjs/mcp/tools/refactor.d.ts.map +1 -0
- package/dist/cjs/mcp/tools/refactor.js +335 -0
- package/dist/cjs/mcp/tools/refactor.js.map +1 -0
- package/dist/cjs/mcp/tools/review.d.ts +10 -0
- package/dist/cjs/mcp/tools/review.d.ts.map +1 -0
- package/dist/cjs/mcp/tools/review.js +283 -0
- package/dist/cjs/mcp/tools/review.js.map +1 -0
- package/dist/cjs/mcp/tools/squeeze-cache.d.ts +14 -0
- package/dist/cjs/mcp/tools/squeeze-cache.d.ts.map +1 -0
- package/dist/cjs/mcp/tools/squeeze-cache.js +418 -0
- package/dist/cjs/mcp/tools/squeeze-cache.js.map +1 -0
- package/dist/cjs/mcp/tools/text-ops.d.ts +14 -0
- package/dist/cjs/mcp/tools/text-ops.d.ts.map +1 -0
- package/dist/cjs/mcp/tools/text-ops.js +155 -0
- package/dist/cjs/mcp/tools/text-ops.js.map +1 -0
- package/dist/cjs/mcp/tools/workspace.d.ts +10 -0
- package/dist/cjs/mcp/tools/workspace.d.ts.map +1 -0
- package/dist/cjs/mcp/tools/workspace.js +675 -0
- package/dist/cjs/mcp/tools/workspace.js.map +1 -0
- package/dist/cjs/mcp/tools.d.ts.map +1 -1
- package/dist/cjs/mcp/tools.js +636 -6388
- package/dist/cjs/mcp/tools.js.map +1 -1
- package/dist/cjs/mcp/utils.d.ts +55 -2
- package/dist/cjs/mcp/utils.d.ts.map +1 -1
- package/dist/cjs/mcp/utils.js +194 -10
- package/dist/cjs/mcp/utils.js.map +1 -1
- package/dist/cjs/mcp/workspaceShared.d.ts +19 -0
- package/dist/cjs/mcp/workspaceShared.d.ts.map +1 -0
- package/dist/cjs/mcp/workspaceShared.js +101 -0
- package/dist/cjs/mcp/workspaceShared.js.map +1 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/utils/ExecAuditLog.d.ts +27 -0
- package/dist/cjs/utils/ExecAuditLog.d.ts.map +1 -0
- package/dist/cjs/utils/ExecAuditLog.js +53 -0
- package/dist/cjs/utils/ExecAuditLog.js.map +1 -0
- package/dist/esm/api/dashboardRoutes.d.ts +3 -3
- package/dist/esm/api/dashboardRoutes.js +1 -1
- package/dist/esm/api/server.d.ts +4 -4
- package/dist/esm/api/server.js +3 -3
- package/dist/esm/autopilot/AutopilotWatcher.js +3 -3
- package/dist/esm/autopilot/Notifier.d.ts +1 -1
- package/dist/esm/cli/lemma-proxy.js +15 -15
- package/dist/esm/cli/stack.d.ts +4 -4
- package/dist/esm/cli/stack.js +5 -5
- package/dist/esm/cloud/KeyManager.d.ts +1 -1
- package/dist/esm/cloud/KeyManager.js +1 -1
- package/dist/esm/cloud/TenantCache.d.ts +1 -1
- package/dist/esm/cloud/identity/ApiKey.d.ts +1 -1
- package/dist/esm/cloud/identity/TenantResolver.d.ts +1 -1
- package/dist/esm/cloud/identity/TenantResolver.js +1 -1
- package/dist/esm/cloud/identity/authenticateV2.d.ts +3 -3
- package/dist/esm/cloud/identity/authenticateV2.js +3 -3
- package/dist/esm/cloud/identity/identityStore.d.ts +2 -2
- package/dist/esm/cloud/identity/index.d.ts +6 -6
- package/dist/esm/cloud/identity/index.js +6 -6
- package/dist/esm/cloud/index.d.ts +3 -3
- package/dist/esm/cloud/index.js +3 -3
- package/dist/esm/cloud/metering/UsageLedger.d.ts +1 -1
- package/dist/esm/cloud/metering/index.d.ts +2 -2
- package/dist/esm/cloud/metering/index.js +2 -2
- package/dist/esm/cloud/scrub/StatelessScrubber.d.ts +1 -1
- package/dist/esm/cloud/scrub/index.d.ts +1 -1
- package/dist/esm/cloud/scrub/index.js +1 -1
- package/dist/esm/cloud/server.d.ts +3 -3
- package/dist/esm/cloud/server.js +5 -5
- package/dist/esm/cloud/store/FileBrainStore.d.ts +1 -1
- package/dist/esm/cloud/store/FileBrainStore.js +1 -1
- package/dist/esm/cloud/store/PostgresBrainStore.d.ts +1 -1
- package/dist/esm/cloud/store/PostgresBrainStore.js +2 -2
- package/dist/esm/cloud/store/index.d.ts +2 -2
- package/dist/esm/cloud/store/index.js +2 -2
- package/dist/esm/cloud/v2/app.d.ts +1 -1
- package/dist/esm/cloud/v2/app.js +3 -3
- package/dist/esm/cloud/v2/routes/index.d.ts +5 -5
- package/dist/esm/cloud/v2/routes/index.js +7 -7
- package/dist/esm/cloud/v2/routes/memories.d.ts +4 -4
- package/dist/esm/cloud/v2/routes/memories.js +2 -2
- package/dist/esm/cloud/v2/routes/recall.d.ts +1 -1
- package/dist/esm/cloud/v2/routes/recall.js +1 -1
- package/dist/esm/cloud/v2/routes/scrub.d.ts +2 -2
- package/dist/esm/cloud/v2/routes/scrub.js +2 -2
- package/dist/esm/cloud/v2/routes/usage.d.ts +1 -1
- package/dist/esm/cloud/v2/routes/verify.d.ts +1 -1
- package/dist/esm/cloud/v2/routes/verify.js +1 -1
- package/dist/esm/cloud/v2/server.js +4 -4
- package/dist/esm/config/index.d.ts +1 -1
- package/dist/esm/config/index.js +1 -1
- package/dist/esm/consensus/index.d.ts +2 -2
- package/dist/esm/consensus/index.js +2 -2
- package/dist/esm/contracts/api-v2.types.d.ts +1 -1
- package/dist/esm/contracts/index.d.ts +4 -4
- package/dist/esm/contracts/index.js +4 -4
- package/dist/esm/core/AgentRegistry.d.ts +1 -1
- package/dist/esm/core/AgentRegistry.js +1 -1
- package/dist/esm/core/DashboardBroadcaster.d.ts +3 -3
- package/dist/esm/core/DashboardBroadcaster.js +2 -2
- package/dist/esm/core/DashboardWebSocketServer.d.ts +1 -1
- package/dist/esm/core/DashboardWebSocketServer.js +1 -1
- package/dist/esm/core/GhostAlchemist.js +1 -1
- package/dist/esm/core/GhostListener.js +2 -2
- package/dist/esm/core/OrchestrationEngine.d.ts +3 -3
- package/dist/esm/core/OrchestrationEngine.js +2 -2
- package/dist/esm/core/SubconsciousEngine.d.ts +1 -1
- package/dist/esm/core/SubconsciousEngine.js +2 -2
- package/dist/esm/core/WebSocketServer.d.ts +2 -2
- package/dist/esm/core/WebSocketServer.js +1 -1
- package/dist/esm/core/index.d.ts +6 -6
- package/dist/esm/core/index.js +6 -6
- package/dist/esm/core/router.d.ts +3 -3
- package/dist/esm/core/router.js +1 -1
- package/dist/esm/db/database.js +1 -1
- package/dist/esm/embed.d.ts +2 -2
- package/dist/esm/embed.js +2 -2
- package/dist/esm/examples/basic-usage.js +1 -1
- package/dist/esm/index.d.ts +14 -14
- package/dist/esm/index.js +14 -14
- package/dist/esm/infra/analyzers/DriftDetector.d.ts +1 -1
- package/dist/esm/infra/analyzers/InfraAnalyzer.d.ts +1 -1
- package/dist/esm/infra/analyzers/MonolithAnalyzer.d.ts +1 -1
- package/dist/esm/infra/analyzers/index.d.ts +3 -3
- package/dist/esm/infra/analyzers/index.js +3 -3
- package/dist/esm/infra/cli.js +2 -2
- package/dist/esm/infra/config.d.ts +1 -1
- package/dist/esm/infra/index.d.ts +5 -5
- package/dist/esm/infra/index.js +5 -5
- package/dist/esm/infra/mcp-tools.js +5 -5
- package/dist/esm/infra/providers/InfraProvider.d.ts +1 -1
- package/dist/esm/infra/providers/JenkinsProvider.d.ts +2 -2
- package/dist/esm/infra/providers/OKDProvider.d.ts +2 -2
- package/dist/esm/infra/providers/TerraformProvider.d.ts +2 -2
- package/dist/esm/infra/providers/index.d.ts +5 -5
- package/dist/esm/infra/providers/index.js +3 -3
- package/dist/esm/mcp/cognitiveMap.d.ts +27 -0
- package/dist/esm/mcp/cognitiveMap.d.ts.map +1 -0
- package/dist/esm/mcp/cognitiveMap.js +40 -0
- package/dist/esm/mcp/cognitiveMap.js.map +1 -0
- package/dist/esm/mcp/index.js +5 -5
- package/dist/esm/mcp/prompts.js +1 -1
- package/dist/esm/mcp/resources.js +2 -2
- package/dist/esm/mcp/tasks.js +1 -1
- package/dist/esm/mcp/tool-profiles.js +1 -1
- package/dist/esm/mcp/toolTypes.d.ts +39 -0
- package/dist/esm/mcp/toolTypes.d.ts.map +1 -0
- package/dist/esm/mcp/toolTypes.js +7 -0
- package/dist/esm/mcp/toolTypes.js.map +1 -0
- package/dist/esm/mcp/tools/analysis.d.ts +11 -0
- package/dist/esm/mcp/tools/analysis.d.ts.map +1 -0
- package/dist/esm/mcp/tools/analysis.js +894 -0
- package/dist/esm/mcp/tools/analysis.js.map +1 -0
- package/dist/esm/mcp/tools/codenav.d.ts +9 -0
- package/dist/esm/mcp/tools/codenav.d.ts.map +1 -0
- package/dist/esm/mcp/tools/codenav.js +485 -0
- package/dist/esm/mcp/tools/codenav.js.map +1 -0
- package/dist/esm/mcp/tools/context-tools.d.ts +20 -0
- package/dist/esm/mcp/tools/context-tools.d.ts.map +1 -0
- package/dist/esm/mcp/tools/context-tools.js +503 -0
- package/dist/esm/mcp/tools/context-tools.js.map +1 -0
- package/dist/esm/mcp/tools/depgraph.d.ts +8 -0
- package/dist/esm/mcp/tools/depgraph.d.ts.map +1 -0
- package/dist/esm/mcp/tools/depgraph.js +315 -0
- package/dist/esm/mcp/tools/depgraph.js.map +1 -0
- package/dist/esm/mcp/tools/dev-utils.d.ts +11 -0
- package/dist/esm/mcp/tools/dev-utils.d.ts.map +1 -0
- package/dist/esm/mcp/tools/dev-utils.js +516 -0
- package/dist/esm/mcp/tools/dev-utils.js.map +1 -0
- package/dist/esm/mcp/tools/devtools.d.ts +9 -0
- package/dist/esm/mcp/tools/devtools.d.ts.map +1 -0
- package/dist/esm/mcp/tools/devtools.js +406 -0
- package/dist/esm/mcp/tools/devtools.js.map +1 -0
- package/dist/esm/mcp/tools/memory.d.ts +10 -0
- package/dist/esm/mcp/tools/memory.d.ts.map +1 -0
- package/dist/esm/mcp/tools/memory.js +533 -0
- package/dist/esm/mcp/tools/memory.js.map +1 -0
- package/dist/esm/mcp/tools/misc.d.ts +10 -0
- package/dist/esm/mcp/tools/misc.d.ts.map +1 -0
- package/dist/esm/mcp/tools/misc.js +417 -0
- package/dist/esm/mcp/tools/misc.js.map +1 -0
- package/dist/esm/mcp/tools/refactor.d.ts +8 -0
- package/dist/esm/mcp/tools/refactor.d.ts.map +1 -0
- package/dist/esm/mcp/tools/refactor.js +296 -0
- package/dist/esm/mcp/tools/refactor.js.map +1 -0
- package/dist/esm/mcp/tools/review.d.ts +10 -0
- package/dist/esm/mcp/tools/review.d.ts.map +1 -0
- package/dist/esm/mcp/tools/review.js +277 -0
- package/dist/esm/mcp/tools/review.js.map +1 -0
- package/dist/esm/mcp/tools/squeeze-cache.d.ts +14 -0
- package/dist/esm/mcp/tools/squeeze-cache.d.ts.map +1 -0
- package/dist/esm/mcp/tools/squeeze-cache.js +382 -0
- package/dist/esm/mcp/tools/squeeze-cache.js.map +1 -0
- package/dist/esm/mcp/tools/text-ops.d.ts +14 -0
- package/dist/esm/mcp/tools/text-ops.d.ts.map +1 -0
- package/dist/esm/mcp/tools/text-ops.js +152 -0
- package/dist/esm/mcp/tools/text-ops.js.map +1 -0
- package/dist/esm/mcp/tools/workspace.d.ts +10 -0
- package/dist/esm/mcp/tools/workspace.d.ts.map +1 -0
- package/dist/esm/mcp/tools/workspace.js +669 -0
- package/dist/esm/mcp/tools/workspace.js.map +1 -0
- package/dist/esm/mcp/tools.d.ts +1 -1
- package/dist/esm/mcp/tools.d.ts.map +1 -1
- package/dist/esm/mcp/tools.js +652 -6404
- package/dist/esm/mcp/tools.js.map +1 -1
- package/dist/esm/mcp/utils.d.ts +55 -2
- package/dist/esm/mcp/utils.d.ts.map +1 -1
- package/dist/esm/mcp/utils.js +189 -10
- package/dist/esm/mcp/utils.js.map +1 -1
- package/dist/esm/mcp/workspaceShared.d.ts +19 -0
- package/dist/esm/mcp/workspaceShared.d.ts.map +1 -0
- package/dist/esm/mcp/workspaceShared.js +93 -0
- package/dist/esm/mcp/workspaceShared.js.map +1 -0
- package/dist/esm/observability/IdeContextSync.d.ts +1 -1
- package/dist/esm/observability/IdeContextSync.js +5 -5
- package/dist/esm/package.json +3 -0
- package/dist/esm/pr-review/PRReviewEngine.d.ts +1 -1
- package/dist/esm/pr-review/PRReviewEngine.js +4 -4
- package/dist/esm/pr-review/ReviewStore.d.ts +1 -1
- package/dist/esm/pr-review/adapters/AzureDevOpsAdapter.d.ts +2 -2
- package/dist/esm/pr-review/adapters/AzureDevOpsAdapter.js +1 -1
- package/dist/esm/pr-review/adapters/GitHubAdapter.d.ts +2 -2
- package/dist/esm/pr-review/adapters/GitHubAdapter.js +1 -1
- package/dist/esm/pr-review/adapters/GitLabAdapter.d.ts +2 -2
- package/dist/esm/pr-review/adapters/GitLabAdapter.js +1 -1
- package/dist/esm/pr-review/adapters/GitPlatformAdapter.d.ts +1 -1
- package/dist/esm/pr-review/adapters/index.d.ts +4 -4
- package/dist/esm/pr-review/adapters/index.js +3 -3
- package/dist/esm/pr-review/analyzers/QualityGrader.d.ts +1 -1
- package/dist/esm/pr-review/analyzers/RegressionDetector.d.ts +1 -1
- package/dist/esm/pr-review/analyzers/SecurityScanner.d.ts +1 -1
- package/dist/esm/pr-review/analyzers/StaticAnalyzer.d.ts +1 -1
- package/dist/esm/pr-review/analyzers/index.d.ts +8 -8
- package/dist/esm/pr-review/analyzers/index.js +4 -4
- package/dist/esm/pr-review/bridge/BrainBridge.d.ts +1 -1
- package/dist/esm/pr-review/bridge/BrainBridge.js +1 -1
- package/dist/esm/pr-review/bridge/index.d.ts +1 -1
- package/dist/esm/pr-review/bridge/index.js +1 -1
- package/dist/esm/pr-review/index.d.ts +4 -4
- package/dist/esm/pr-review/index.js +2 -2
- package/dist/esm/pr-review/reviewRunner.d.ts +2 -2
- package/dist/esm/pr-review/reviewRunner.js +6 -6
- package/dist/esm/pr-review/webhook/WebhookServer.js +1 -1
- package/dist/esm/pr-review/webhook/index.d.ts +1 -1
- package/dist/esm/pr-review/webhook/index.js +1 -1
- package/dist/esm/protocol/flows.d.ts +1 -1
- package/dist/esm/protocol/flows.js +1 -1
- package/dist/esm/protocol/index.d.ts +4 -4
- package/dist/esm/protocol/index.js +4 -4
- package/dist/esm/protocol/utils.d.ts +1 -1
- package/dist/esm/protocol/utils.js +2 -2
- package/dist/esm/protocol/validators.d.ts +1 -1
- package/dist/esm/protocol/validators.js +1 -1
- package/dist/esm/proxy/ContextInjector.d.ts +1 -1
- package/dist/esm/proxy/Gatekeeper.d.ts +1 -1
- package/dist/esm/security/SecurityMiddleware.d.ts +1 -1
- package/dist/esm/security/SecurityMiddleware.js +3 -3
- package/dist/esm/security/index.d.ts +4 -4
- package/dist/esm/security/index.js +4 -4
- package/dist/esm/speculative/SpeculativeCache.d.ts +2 -2
- package/dist/esm/speculative/SpeculativeEngine.d.ts +2 -2
- package/dist/esm/speculative/SpeculativeEngine.js +3 -3
- package/dist/esm/speculative/WorkerPool.d.ts +1 -1
- package/dist/esm/speculative/index.d.ts +4 -4
- package/dist/esm/speculative/index.js +4 -4
- package/dist/esm/subconscious/GitIngest.d.ts +1 -1
- package/dist/esm/subconscious/GitIngest.js +1 -1
- package/dist/esm/subconscious/SemanticCache.d.ts +2 -2
- package/dist/esm/subconscious/SemanticCache.js +2 -2
- package/dist/esm/subconscious/SubconsciousEngine.d.ts +1 -1
- package/dist/esm/subconscious/SubconsciousEngine.js +3 -3
- package/dist/esm/subconscious/TheBrainV2.d.ts +1 -1
- package/dist/esm/subconscious/TheBrainV2.js +2 -2
- package/dist/esm/subconscious/freshness/LocalFsResolver.d.ts +1 -1
- package/dist/esm/subconscious/freshness/LocalFsResolver.js +1 -1
- package/dist/esm/subconscious/freshness/SuppliedEvidenceResolver.d.ts +1 -1
- package/dist/esm/subconscious/freshness/index.d.ts +2 -2
- package/dist/esm/subconscious/freshness/index.js +2 -2
- package/dist/esm/subconscious/index.d.ts +4 -4
- package/dist/esm/subconscious/index.js +4 -4
- package/dist/esm/utils/AffectedTests.js +1 -1
- package/dist/esm/utils/AutoContextBundle.d.ts +1 -1
- package/dist/esm/utils/AutoContextBundle.js +4 -4
- package/dist/esm/utils/BulkFileDigest.js +2 -2
- package/dist/esm/utils/ClipboardWatcher.js +2 -2
- package/dist/esm/utils/ContextCollapser.js +1 -1
- package/dist/esm/utils/ConversationCheckpoint.js +1 -1
- package/dist/esm/utils/ExecAuditLog.d.ts +27 -0
- package/dist/esm/utils/ExecAuditLog.d.ts.map +1 -0
- package/dist/esm/utils/ExecAuditLog.js +45 -0
- package/dist/esm/utils/ExecAuditLog.js.map +1 -0
- package/dist/esm/utils/ExecutiveReport.js +1 -1
- package/dist/esm/utils/OutputLedger.js +2 -2
- package/dist/esm/utils/ReadWorkspaceCache.js +1 -1
- package/dist/esm/utils/SpeculativeWarmer.d.ts +1 -1
- package/dist/esm/utils/SpeculativeWarmer.js +2 -2
- package/dist/esm/utils/StateHashCache.js +1 -1
- package/dist/esm/utils/StructuralSearch.d.ts +1 -1
- package/dist/esm/utils/SurgicalASTInsert.js +1 -1
- package/dist/esm/utils/SymbolSurgicalContext.js +1 -1
- package/dist/esm/utils/TestAutofixInterceptor.js +1 -1
- package/dist/esm/utils/WorkspaceSearch.js +0 -0
- package/dist/esm/utils/logger.js +1 -1
- package/dist/esm/utils/reportSavings.js +1 -1
- package/package.json +3 -1
- package/sdks/crewai/dist/crewai/src/index.d.ts +131 -0
- package/sdks/crewai/dist/crewai/src/index.js +325 -0
- package/sdks/crewai/dist/crewai/src/memory.d.ts +36 -0
- package/sdks/crewai/dist/crewai/src/memory.js +61 -0
- package/sdks/crewai/dist/ts/src/client.d.ts +39 -0
- package/sdks/crewai/dist/ts/src/client.js +124 -0
- package/sdks/crewai/dist/ts/src/errors.d.ts +19 -0
- package/sdks/crewai/dist/ts/src/errors.js +18 -0
- package/sdks/crewai/dist/ts/src/evidence.d.ts +30 -0
- package/sdks/crewai/dist/ts/src/evidence.js +175 -0
- package/sdks/crewai/dist/ts/src/index.d.ts +4 -0
- package/sdks/crewai/dist/ts/src/index.js +27 -0
- package/sdks/crewai/dist/ts/src/types.d.ts +112 -0
- package/sdks/crewai/dist/ts/src/types.js +11 -0
|
@@ -0,0 +1,894 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zero-token static analysis tools: entropy_score, coupling_radar, pattern_fossil,
|
|
3
|
+
* git_heatmap_risk, precrime_static, semantic_dedup_guard, dead_export_necromancer.
|
|
4
|
+
* All but semantic_dedup_guard are pure TypeScript-Compiler-API/git analysis — no LLM,
|
|
5
|
+
* no Brain. Split out of ../tools.ts (see that file's history for why) — pure
|
|
6
|
+
* relocation, no behavior change.
|
|
7
|
+
*/
|
|
8
|
+
import fs from "fs";
|
|
9
|
+
import path from "path";
|
|
10
|
+
import { execSync } from "child_process";
|
|
11
|
+
import * as ts from "typescript";
|
|
12
|
+
import { getBrain, deriveProjectId } from "../../subconscious/TheBrainV2.js";
|
|
13
|
+
import { safeResolvePath, logError } from "../utils.js";
|
|
14
|
+
export const ANALYSIS_TOOL_DEFINITIONS = [
|
|
15
|
+
{
|
|
16
|
+
name: "entropy_score",
|
|
17
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
18
|
+
description: "Calcula la entropía matemática (complejidad) de uno o más archivos usando el TypeScript Compiler API. Sin LLM. Sin tokens. Devuelve: cyclomatic complexity, nesting depth, ratio de 'any', tamaño de funciones, y un score compuesto 0-100 (0=limpio, 100=caos puro). Usa esto para identificar qué archivos necesitan refactor ANTES de tocarlos.",
|
|
19
|
+
inputSchema: {
|
|
20
|
+
type: "object",
|
|
21
|
+
properties: {
|
|
22
|
+
filePath: { type: "string", description: "Archivo o directorio a analizar (relativo al workspace root)" },
|
|
23
|
+
topN: { type: "number", description: "Solo reportar los N archivos más caóticos (default: 10)", default: 10 },
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
name: "coupling_radar",
|
|
29
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
30
|
+
description: "Construye un grafo de acoplamiento entre módulos analizando imports/exports con el TypeScript Compiler. Sin LLM. Sin tokens. Detecta: ciclos de dependencia, módulos 'dios' (importados por todo), islas muertas (nadie los importa), y fan-in/fan-out por módulo. Esencial antes de refactors grandes.",
|
|
31
|
+
inputSchema: {
|
|
32
|
+
type: "object",
|
|
33
|
+
properties: {
|
|
34
|
+
dirPath: { type: "string", description: "Directorio a analizar (relativo al workspace root)", default: "src" },
|
|
35
|
+
minFanIn: { type: "number", description: "Reportar solo módulos importados por al menos N archivos", default: 3 },
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
name: "pattern_fossil",
|
|
41
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
42
|
+
description: "Detecta código zombie: patrones que ya fueron reemplazados en la mayoría del codebase pero siguen vivos en archivos viejos. Sin LLM. Sin tokens. Detecta: callbacks vs async/await, var vs let/const, require() vs import, any vs generics. Devuelve porcentaje de adopción del patrón nuevo vs. los fósiles que quedan.",
|
|
43
|
+
inputSchema: {
|
|
44
|
+
type: "object",
|
|
45
|
+
properties: {
|
|
46
|
+
dirPath: { type: "string", description: "Directorio a escanear (default: src)", default: "src" },
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
name: "git_heatmap_risk",
|
|
52
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
53
|
+
description: "Analiza el historial de git para identificar zonas de alto riesgo. Sin LLM. Sin tokens. Calcula: churn rate (frecuencia de cambios), co-edición oculta (archivos que siempre cambian juntos = acoplamiento implícito), y un risk score compuesto por archivo. Detecta los archivos que estadísticamente tienen más probabilidad de tener un bug.",
|
|
54
|
+
inputSchema: {
|
|
55
|
+
type: "object",
|
|
56
|
+
properties: {
|
|
57
|
+
days: { type: "number", description: "Ventana de días de historial a analizar (default: 30)", default: 30 },
|
|
58
|
+
topN: { type: "number", description: "Top N archivos más riesgosos a reportar (default: 10)", default: 10 },
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
name: "precrime_static",
|
|
64
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
65
|
+
description: "MINORITY REPORT para tu código. Combina entropy_score + coupling_radar + git_heatmap_risk en un predictor de riesgo compuesto. Sin LLM. Sin tokens. Devuelve un ranking de archivos y funciones con mayor probabilidad de causar un bug, con justificación matemática de cada factor. Úsalo antes de un deploy o un PR review.",
|
|
66
|
+
inputSchema: {
|
|
67
|
+
type: "object",
|
|
68
|
+
properties: {
|
|
69
|
+
dirPath: { type: "string", description: "Directorio a analizar (default: src)", default: "src" },
|
|
70
|
+
days: { type: "number", description: "Ventana de días de historial git (default: 30)", default: 30 },
|
|
71
|
+
topN: { type: "number", description: "Top N archivos a reportar (default: 10)", default: 10 },
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
name: "semantic_dedup_guard",
|
|
77
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
78
|
+
description: "Firewall anti-redundancia para The Brain. Antes de hacer store_memory, pasa el contenido por aquí. Consulta ChromaDB localmente sin gastar tokens. Si hay un hit >= 92% → rechaza el store y devuelve el duplicado. Si 75-91% → advierte y muestra el similar. Mantiene The Brain denso y limpio.",
|
|
79
|
+
inputSchema: {
|
|
80
|
+
type: "object",
|
|
81
|
+
properties: {
|
|
82
|
+
query: { type: "string", description: "El query/prompt que ibas a almacenar" },
|
|
83
|
+
response: { type: "string", description: "La respuesta que ibas a almacenar" },
|
|
84
|
+
threshold: { type: "number", description: "Threshold de duplicado exacto (default: 0.92)", default: 0.92 },
|
|
85
|
+
},
|
|
86
|
+
required: ["query", "response"],
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
name: "dead_export_necromancer",
|
|
91
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
92
|
+
description: "Resucita el código muerto. Usa el TypeScript Compiler para mapear TODOS los exports del workspace y los cruza contra TODOS los imports. Lo que se exporta pero nadie importa = código zombie que está inflando tu contexto y desperdiciando tokens. Sin LLM. Sin tokens. Devuelve lista de exports muertos con estimación de tokens desperdiciados.",
|
|
93
|
+
inputSchema: {
|
|
94
|
+
type: "object",
|
|
95
|
+
properties: {
|
|
96
|
+
dirPath: { type: "string", description: "Directorio a analizar (default: src)", default: "src" },
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
];
|
|
101
|
+
function computeCyclomaticComplexity(node) {
|
|
102
|
+
let count = 1; // base
|
|
103
|
+
function walk(n) {
|
|
104
|
+
switch (n.kind) {
|
|
105
|
+
case ts.SyntaxKind.IfStatement:
|
|
106
|
+
case ts.SyntaxKind.WhileStatement:
|
|
107
|
+
case ts.SyntaxKind.ForStatement:
|
|
108
|
+
case ts.SyntaxKind.ForInStatement:
|
|
109
|
+
case ts.SyntaxKind.ForOfStatement:
|
|
110
|
+
case ts.SyntaxKind.CaseClause:
|
|
111
|
+
case ts.SyntaxKind.CatchClause:
|
|
112
|
+
case ts.SyntaxKind.ConditionalExpression:
|
|
113
|
+
count++;
|
|
114
|
+
break;
|
|
115
|
+
case ts.SyntaxKind.BinaryExpression: {
|
|
116
|
+
const bin = n;
|
|
117
|
+
if (bin.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken ||
|
|
118
|
+
bin.operatorToken.kind === ts.SyntaxKind.BarBarToken ||
|
|
119
|
+
bin.operatorToken.kind === ts.SyntaxKind.QuestionQuestionToken)
|
|
120
|
+
count++;
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
ts.forEachChild(n, walk);
|
|
125
|
+
}
|
|
126
|
+
walk(node);
|
|
127
|
+
return count;
|
|
128
|
+
}
|
|
129
|
+
function computeMaxNesting(node, depth = 0) {
|
|
130
|
+
const nestingNodes = new Set([
|
|
131
|
+
ts.SyntaxKind.IfStatement, ts.SyntaxKind.WhileStatement,
|
|
132
|
+
ts.SyntaxKind.ForStatement, ts.SyntaxKind.ForInStatement,
|
|
133
|
+
ts.SyntaxKind.ForOfStatement, ts.SyntaxKind.SwitchStatement,
|
|
134
|
+
ts.SyntaxKind.TryStatement, ts.SyntaxKind.Block,
|
|
135
|
+
]);
|
|
136
|
+
const isNesting = nestingNodes.has(node.kind);
|
|
137
|
+
const currentDepth = isNesting ? depth + 1 : depth;
|
|
138
|
+
let max = currentDepth;
|
|
139
|
+
ts.forEachChild(node, child => {
|
|
140
|
+
max = Math.max(max, computeMaxNesting(child, currentDepth));
|
|
141
|
+
});
|
|
142
|
+
return max;
|
|
143
|
+
}
|
|
144
|
+
function analyzeFileEntropy(filePath, relPath) {
|
|
145
|
+
try {
|
|
146
|
+
const src = fs.readFileSync(filePath, "utf8");
|
|
147
|
+
const sourceFile = ts.createSourceFile(filePath, src, ts.ScriptTarget.Latest, true);
|
|
148
|
+
const lines = src.split("\n");
|
|
149
|
+
// Count 'any' tokens
|
|
150
|
+
const anyCount = (src.match(/:\s*any\b/g) || []).length;
|
|
151
|
+
const typeAnnotations = (src.match(/:\s*\w+/g) || []).length;
|
|
152
|
+
const anyRatio = typeAnnotations > 0 ? anyCount / typeAnnotations : 0;
|
|
153
|
+
const functions = [];
|
|
154
|
+
function visitFunctions(node) {
|
|
155
|
+
const isFn = ts.isFunctionDeclaration(node) ||
|
|
156
|
+
ts.isMethodDeclaration(node) ||
|
|
157
|
+
ts.isArrowFunction(node) ||
|
|
158
|
+
ts.isFunctionExpression(node);
|
|
159
|
+
if (isFn) {
|
|
160
|
+
const nameNode = node.name;
|
|
161
|
+
const name = nameNode ? nameNode.getText(sourceFile) : "<anonymous>";
|
|
162
|
+
const start = sourceFile.getLineAndCharacterOfPosition(node.getStart()).line;
|
|
163
|
+
const end = sourceFile.getLineAndCharacterOfPosition(node.getEnd()).line;
|
|
164
|
+
const fnLines = end - start + 1;
|
|
165
|
+
const cc = computeCyclomaticComplexity(node);
|
|
166
|
+
const depth = computeMaxNesting(node);
|
|
167
|
+
functions.push({ name, lines: fnLines, cyclomaticComplexity: cc, nestingDepth: depth });
|
|
168
|
+
}
|
|
169
|
+
ts.forEachChild(node, visitFunctions);
|
|
170
|
+
}
|
|
171
|
+
visitFunctions(sourceFile);
|
|
172
|
+
const avgCC = functions.length > 0
|
|
173
|
+
? functions.reduce((s, f) => s + f.cyclomaticComplexity, 0) / functions.length
|
|
174
|
+
: 1;
|
|
175
|
+
const maxDepth = functions.length > 0
|
|
176
|
+
? Math.max(...functions.map(f => f.nestingDepth))
|
|
177
|
+
: 0;
|
|
178
|
+
const avgLines = functions.length > 0
|
|
179
|
+
? functions.reduce((s, f) => s + f.lines, 0) / functions.length
|
|
180
|
+
: lines.length;
|
|
181
|
+
// Entropy score: weighted composite 0-100
|
|
182
|
+
const ccScore = Math.min(100, (avgCC / 15) * 40); // 40% weight
|
|
183
|
+
const nestScore = Math.min(100, (maxDepth / 8) * 20); // 20% weight
|
|
184
|
+
const anyScore = Math.min(100, anyRatio * 100) * 0.20; // 20% weight
|
|
185
|
+
const sizeScore = Math.min(100, (avgLines / 80) * 100) * 0.20; // 20% weight
|
|
186
|
+
const entropyScore = Math.round(ccScore + nestScore + anyScore + sizeScore);
|
|
187
|
+
const verdict = entropyScore >= 80 ? "🔥 PELIGRO — Refactor urgente" :
|
|
188
|
+
entropyScore >= 60 ? "⚠️ ALTO — Revisar antes de tocar" :
|
|
189
|
+
entropyScore >= 40 ? "🟡 MEDIO — Manejable" :
|
|
190
|
+
"✅ LIMPIO — Bajo riesgo";
|
|
191
|
+
return {
|
|
192
|
+
file: relPath,
|
|
193
|
+
entropyScore,
|
|
194
|
+
cyclomaticComplexity: Math.round(avgCC * 10) / 10,
|
|
195
|
+
maxNestingDepth: maxDepth,
|
|
196
|
+
anyRatio: Math.round(anyRatio * 1000) / 10,
|
|
197
|
+
avgFunctionLines: Math.round(avgLines),
|
|
198
|
+
functionCount: functions.length,
|
|
199
|
+
functions: functions.sort((a, b) => b.cyclomaticComplexity - a.cyclomaticComplexity).slice(0, 5),
|
|
200
|
+
verdict,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
async function handleEntropyScore(args) {
|
|
208
|
+
const filePath = args?.filePath || "";
|
|
209
|
+
const topN = args?.topN || 10;
|
|
210
|
+
const workspaceRoot = process.cwd();
|
|
211
|
+
try {
|
|
212
|
+
const { resolved } = safeResolvePath(workspaceRoot, filePath);
|
|
213
|
+
const isFile = fs.statSync(resolved).isFile();
|
|
214
|
+
const filesToAnalyze = [];
|
|
215
|
+
if (isFile) {
|
|
216
|
+
filesToAnalyze.push({ abs: resolved, rel: filePath });
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
function walkForTs(dir, rel) {
|
|
220
|
+
try {
|
|
221
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
222
|
+
for (const e of entries) {
|
|
223
|
+
if (["node_modules", ".git", "dist", "chroma_data", ".lemma"].includes(e.name))
|
|
224
|
+
continue;
|
|
225
|
+
const abs = path.join(dir, e.name);
|
|
226
|
+
const relPath2 = rel ? path.join(rel, e.name) : e.name;
|
|
227
|
+
if (e.isDirectory())
|
|
228
|
+
walkForTs(abs, relPath2);
|
|
229
|
+
else if (e.name.match(/\.(ts|tsx)$/) && !e.name.endsWith(".d.ts")) {
|
|
230
|
+
filesToAnalyze.push({ abs, rel: relPath2 });
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
catch { }
|
|
235
|
+
}
|
|
236
|
+
walkForTs(resolved, filePath);
|
|
237
|
+
}
|
|
238
|
+
const results = filesToAnalyze
|
|
239
|
+
.map(f => analyzeFileEntropy(f.abs, f.rel))
|
|
240
|
+
.filter((r) => r !== null)
|
|
241
|
+
.sort((a, b) => b.entropyScore - a.entropyScore)
|
|
242
|
+
.slice(0, topN);
|
|
243
|
+
const avgEntropy = results.length > 0
|
|
244
|
+
? Math.round(results.reduce((s, r) => s + r.entropyScore, 0) / results.length)
|
|
245
|
+
: 0;
|
|
246
|
+
const summary = [
|
|
247
|
+
`# 🧮 Entropy Score Report`,
|
|
248
|
+
`Archivos analizados: ${filesToAnalyze.length} | Top ${topN} mostrados | Entropía promedio: ${avgEntropy}/100`,
|
|
249
|
+
``,
|
|
250
|
+
...results.map((r, i) => `## ${i + 1}. ${r.file} — Score: ${r.entropyScore}/100 ${r.verdict}\n` +
|
|
251
|
+
` CC: ${r.cyclomaticComplexity} | Nesting: ${r.maxNestingDepth} | Any ratio: ${r.anyRatio}% | Avg fn: ${r.avgFunctionLines} líneas | Fns: ${r.functionCount}\n` +
|
|
252
|
+
(r.functions.length > 0
|
|
253
|
+
? ` Top funciones caóticas: ${r.functions.slice(0, 3).map(f => `${f.name}(CC:${f.cyclomaticComplexity})`).join(", ")}`
|
|
254
|
+
: "")),
|
|
255
|
+
].join("\n");
|
|
256
|
+
return { content: [{ type: "text", text: summary }] };
|
|
257
|
+
}
|
|
258
|
+
catch (err) {
|
|
259
|
+
logError("entropy_score", err);
|
|
260
|
+
return { content: [{ type: "text", text: `Entropy score failed: ${err.message}` }] };
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
function extractImports(filePath, relPath, workspaceRoot) {
|
|
264
|
+
try {
|
|
265
|
+
const src = fs.readFileSync(filePath, "utf8");
|
|
266
|
+
const sourceFile = ts.createSourceFile(filePath, src, ts.ScriptTarget.Latest, true);
|
|
267
|
+
const imports = [];
|
|
268
|
+
ts.forEachChild(sourceFile, node => {
|
|
269
|
+
if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier)) {
|
|
270
|
+
const spec = node.moduleSpecifier.text;
|
|
271
|
+
if (spec.startsWith(".")) {
|
|
272
|
+
// Resolve relative import to normalized path
|
|
273
|
+
const dir = path.dirname(filePath);
|
|
274
|
+
let resolved = path.resolve(dir, spec);
|
|
275
|
+
// Try to find the actual file with extension
|
|
276
|
+
for (const ext of [".ts", ".tsx", "/index.ts", "/index.tsx"]) {
|
|
277
|
+
if (fs.existsSync(resolved + ext)) {
|
|
278
|
+
resolved = resolved + ext;
|
|
279
|
+
break;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
const rel = path.relative(workspaceRoot, resolved).replace(/\\/g, "/");
|
|
283
|
+
imports.push(rel);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
return imports;
|
|
288
|
+
}
|
|
289
|
+
catch {
|
|
290
|
+
return [];
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
async function handleCouplingRadar(args) {
|
|
294
|
+
const dirPath = args?.dirPath || "src";
|
|
295
|
+
const minFanIn = args?.minFanIn || 3;
|
|
296
|
+
const workspaceRoot = process.cwd();
|
|
297
|
+
try {
|
|
298
|
+
const { resolved } = safeResolvePath(workspaceRoot, dirPath);
|
|
299
|
+
const allFiles = [];
|
|
300
|
+
function walkForTs2(dir, rel) {
|
|
301
|
+
try {
|
|
302
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
303
|
+
for (const e of entries) {
|
|
304
|
+
if (["node_modules", ".git", "dist", "chroma_data", ".lemma"].includes(e.name))
|
|
305
|
+
continue;
|
|
306
|
+
const abs = path.join(dir, e.name);
|
|
307
|
+
const relPath2 = rel ? path.join(rel, e.name).replace(/\\/g, "/") : e.name;
|
|
308
|
+
if (e.isDirectory())
|
|
309
|
+
walkForTs2(abs, relPath2);
|
|
310
|
+
else if (e.name.match(/\.(ts|tsx)$/) && !e.name.endsWith(".d.ts")) {
|
|
311
|
+
allFiles.push({ abs, rel: relPath2 });
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
catch { }
|
|
316
|
+
}
|
|
317
|
+
walkForTs2(resolved, dirPath);
|
|
318
|
+
// Build adjacency map
|
|
319
|
+
const moduleMap = new Map();
|
|
320
|
+
for (const f of allFiles) {
|
|
321
|
+
moduleMap.set(f.rel, { file: f.rel, imports: [], importedBy: [], fanIn: 0, fanOut: 0 });
|
|
322
|
+
}
|
|
323
|
+
for (const f of allFiles) {
|
|
324
|
+
const imports = extractImports(f.abs, f.rel, workspaceRoot);
|
|
325
|
+
const node = moduleMap.get(f.rel);
|
|
326
|
+
for (const imp of imports) {
|
|
327
|
+
if (moduleMap.has(imp)) {
|
|
328
|
+
node.imports.push(imp);
|
|
329
|
+
moduleMap.get(imp).importedBy.push(f.rel);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
// Compute fan-in / fan-out
|
|
334
|
+
for (const node of moduleMap.values()) {
|
|
335
|
+
node.fanIn = node.importedBy.length;
|
|
336
|
+
node.fanOut = node.imports.length;
|
|
337
|
+
}
|
|
338
|
+
// Detect cycles using DFS
|
|
339
|
+
const cycles = [];
|
|
340
|
+
const visited = new Set();
|
|
341
|
+
const stack = new Set();
|
|
342
|
+
function dfs(file, currentPath) {
|
|
343
|
+
if (stack.has(file)) {
|
|
344
|
+
const cycleStart = currentPath.indexOf(file);
|
|
345
|
+
if (cycleStart >= 0)
|
|
346
|
+
cycles.push(currentPath.slice(cycleStart).concat(file));
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
if (visited.has(file))
|
|
350
|
+
return;
|
|
351
|
+
visited.add(file);
|
|
352
|
+
stack.add(file);
|
|
353
|
+
const node = moduleMap.get(file);
|
|
354
|
+
if (node) {
|
|
355
|
+
for (const imp of node.imports)
|
|
356
|
+
dfs(imp, [...currentPath, file]);
|
|
357
|
+
}
|
|
358
|
+
stack.delete(file);
|
|
359
|
+
}
|
|
360
|
+
for (const f of allFiles)
|
|
361
|
+
dfs(f.rel, []);
|
|
362
|
+
// High fan-in (god modules)
|
|
363
|
+
const godModules = [...moduleMap.values()]
|
|
364
|
+
.filter(n => n.fanIn >= minFanIn)
|
|
365
|
+
.sort((a, b) => b.fanIn - a.fanIn)
|
|
366
|
+
.slice(0, 10);
|
|
367
|
+
// Dead islands (no fan-in, not an index/entry)
|
|
368
|
+
const deadIslands = [...moduleMap.values()]
|
|
369
|
+
.filter(n => n.fanIn === 0 && n.fanOut === 0 && !n.file.includes("index"))
|
|
370
|
+
.slice(0, 10);
|
|
371
|
+
const report = [
|
|
372
|
+
`# 🕸️ Coupling Radar`,
|
|
373
|
+
`Módulos analizados: ${moduleMap.size}`,
|
|
374
|
+
``,
|
|
375
|
+
`## 🔄 Ciclos de dependencia detectados: ${cycles.length}`,
|
|
376
|
+
cycles.slice(0, 5).map(c => ` ⚡ ${c.join(" → ")}`).join("\n") || " Ninguno detectado ✅",
|
|
377
|
+
``,
|
|
378
|
+
`## 👁️ Módulos DIOS (fan-in >= ${minFanIn}):`,
|
|
379
|
+
godModules.map(n => ` 🌐 ${n.file}\n fan-in: ${n.fanIn} importadores | fan-out: ${n.fanOut} dependencias`).join("\n") || " Ninguno",
|
|
380
|
+
``,
|
|
381
|
+
`## 👻 Islas muertas (sin importadores y sin dependencias):`,
|
|
382
|
+
deadIslands.map(n => ` 💀 ${n.file}`).join("\n") || " Ninguno ✅",
|
|
383
|
+
``,
|
|
384
|
+
`## 📊 Top acoplados (mayor fan-in + fan-out):`,
|
|
385
|
+
[...moduleMap.values()]
|
|
386
|
+
.sort((a, b) => (b.fanIn + b.fanOut) - (a.fanIn + a.fanOut))
|
|
387
|
+
.slice(0, 8)
|
|
388
|
+
.map(n => ` ${n.file} → in:${n.fanIn} out:${n.fanOut}`)
|
|
389
|
+
.join("\n"),
|
|
390
|
+
].join("\n");
|
|
391
|
+
return { content: [{ type: "text", text: report }] };
|
|
392
|
+
}
|
|
393
|
+
catch (err) {
|
|
394
|
+
logError("coupling_radar", err);
|
|
395
|
+
return { content: [{ type: "text", text: `Coupling radar failed: ${err.message}` }] };
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
const FOSSIL_PATTERNS = [
|
|
399
|
+
{
|
|
400
|
+
name: "async/await vs callbacks",
|
|
401
|
+
modernPattern: /async\s+function|\basync\s*\(|await\s+/g,
|
|
402
|
+
fossilPattern: /\.then\s*\(|\.catch\s*\(|callback\s*\(/g,
|
|
403
|
+
modernLabel: "async/await",
|
|
404
|
+
fossilLabel: "callbacks/.then",
|
|
405
|
+
},
|
|
406
|
+
{
|
|
407
|
+
name: "const/let vs var",
|
|
408
|
+
modernPattern: /\b(const|let)\s+\w+/g,
|
|
409
|
+
fossilPattern: /\bvar\s+\w+/g,
|
|
410
|
+
modernLabel: "const/let",
|
|
411
|
+
fossilLabel: "var",
|
|
412
|
+
},
|
|
413
|
+
{
|
|
414
|
+
name: "ESM import vs CommonJS require",
|
|
415
|
+
modernPattern: /^import\s+/gm,
|
|
416
|
+
fossilPattern: /\brequire\s*\(/g,
|
|
417
|
+
modernLabel: "ESM import",
|
|
418
|
+
fossilLabel: "require()",
|
|
419
|
+
},
|
|
420
|
+
{
|
|
421
|
+
name: "Typed generics vs any",
|
|
422
|
+
modernPattern: /<[A-Z]\w*>/g,
|
|
423
|
+
fossilPattern: /:\s*any\b/g,
|
|
424
|
+
modernLabel: "generics/types",
|
|
425
|
+
fossilLabel: ": any",
|
|
426
|
+
},
|
|
427
|
+
{
|
|
428
|
+
name: "Template literals vs concatenation",
|
|
429
|
+
modernPattern: /`[^`]*\$\{/g,
|
|
430
|
+
fossilPattern: /["']\s*\+\s*\w+\s*\+\s*["']/g,
|
|
431
|
+
modernLabel: "template literals",
|
|
432
|
+
fossilLabel: "string concatenation",
|
|
433
|
+
},
|
|
434
|
+
];
|
|
435
|
+
async function handlePatternFossil(args) {
|
|
436
|
+
const dirPath = args?.dirPath || "src";
|
|
437
|
+
const workspaceRoot = process.cwd();
|
|
438
|
+
try {
|
|
439
|
+
const { resolved } = safeResolvePath(workspaceRoot, dirPath);
|
|
440
|
+
const allFiles = [];
|
|
441
|
+
function walkForSource(dir, rel) {
|
|
442
|
+
try {
|
|
443
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
444
|
+
for (const e of entries) {
|
|
445
|
+
if (["node_modules", ".git", "dist", "chroma_data", ".lemma"].includes(e.name))
|
|
446
|
+
continue;
|
|
447
|
+
const abs = path.join(dir, e.name);
|
|
448
|
+
const relPath2 = rel ? path.join(rel, e.name) : e.name;
|
|
449
|
+
if (e.isDirectory())
|
|
450
|
+
walkForSource(abs, relPath2);
|
|
451
|
+
else if (e.name.match(/\.(ts|tsx|js|jsx)$/) && !e.name.endsWith(".d.ts")) {
|
|
452
|
+
allFiles.push({ abs, rel: relPath2 });
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
catch { }
|
|
457
|
+
}
|
|
458
|
+
walkForSource(resolved, dirPath);
|
|
459
|
+
const results = [];
|
|
460
|
+
for (const fp of FOSSIL_PATTERNS) {
|
|
461
|
+
let totalModern = 0;
|
|
462
|
+
let totalFossil = 0;
|
|
463
|
+
const fossilFiles = [];
|
|
464
|
+
for (const f of allFiles) {
|
|
465
|
+
try {
|
|
466
|
+
const src = fs.readFileSync(f.abs, "utf8");
|
|
467
|
+
const modernMatches = (src.match(fp.modernPattern) || []).length;
|
|
468
|
+
const fossilMatches = (src.match(fp.fossilPattern) || []).length;
|
|
469
|
+
totalModern += modernMatches;
|
|
470
|
+
if (fossilMatches > 0) {
|
|
471
|
+
totalFossil += fossilMatches;
|
|
472
|
+
fossilFiles.push(`${f.rel} (${fossilMatches}x)`);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
catch { }
|
|
476
|
+
}
|
|
477
|
+
const total = totalModern + totalFossil;
|
|
478
|
+
const adoptionPercent = total > 0 ? Math.round((totalModern / total) * 100) : 100;
|
|
479
|
+
if (totalFossil > 0) {
|
|
480
|
+
results.push({
|
|
481
|
+
pattern: fp.name,
|
|
482
|
+
modernCount: totalModern,
|
|
483
|
+
fossilCount: totalFossil,
|
|
484
|
+
adoptionPercent,
|
|
485
|
+
fossilFiles: fossilFiles.slice(0, 5),
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
if (results.length === 0) {
|
|
490
|
+
return { content: [{ type: "text", text: "✅ No se detectaron fósiles de código. Codebase moderno y consistente." }] };
|
|
491
|
+
}
|
|
492
|
+
const report = [
|
|
493
|
+
`# 👻 Pattern Fossil Detector`,
|
|
494
|
+
`Archivos escaneados: ${allFiles.length}`,
|
|
495
|
+
``,
|
|
496
|
+
...results.map(r => [
|
|
497
|
+
`## ${r.adoptionPercent < 70 ? "🦕" : "⚠️"} ${r.pattern}`,
|
|
498
|
+
`Adopción moderna: **${r.adoptionPercent}%** | Modern: ${r.modernCount} | Fósil: ${r.fossilCount}`,
|
|
499
|
+
`Archivos con código fósil:`,
|
|
500
|
+
r.fossilFiles.map(f => ` - ${f}`).join("\n"),
|
|
501
|
+
].join("\n")),
|
|
502
|
+
].join("\n\n");
|
|
503
|
+
return { content: [{ type: "text", text: report }] };
|
|
504
|
+
}
|
|
505
|
+
catch (err) {
|
|
506
|
+
logError("pattern_fossil", err);
|
|
507
|
+
return { content: [{ type: "text", text: `Pattern fossil failed: ${err.message}` }] };
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
// ─── git_heatmap_risk ─────────────────────────────────────────────────────────
|
|
511
|
+
async function handleGitHeatmapRisk(args) {
|
|
512
|
+
const days = args?.days || 30;
|
|
513
|
+
const topN = args?.topN || 10;
|
|
514
|
+
const workspaceRoot = process.cwd();
|
|
515
|
+
try {
|
|
516
|
+
const since = new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString().split("T")[0];
|
|
517
|
+
// Get commit log with changed files
|
|
518
|
+
let gitLog = "";
|
|
519
|
+
try {
|
|
520
|
+
gitLog = execSync(`git log --since="${since}" --name-only --pretty=format:"COMMIT:%H" --diff-filter=M`, { cwd: workspaceRoot, encoding: "utf8", timeout: 10000 });
|
|
521
|
+
}
|
|
522
|
+
catch {
|
|
523
|
+
return { content: [{ type: "text", text: "⚠️ Git no disponible o repositorio sin historial suficiente." }] };
|
|
524
|
+
}
|
|
525
|
+
// Parse commit groups
|
|
526
|
+
const commitGroups = [];
|
|
527
|
+
let currentGroup = [];
|
|
528
|
+
for (const line of gitLog.split("\n")) {
|
|
529
|
+
const trimmed = line.trim();
|
|
530
|
+
if (trimmed.startsWith("COMMIT:")) {
|
|
531
|
+
if (currentGroup.length > 0)
|
|
532
|
+
commitGroups.push(currentGroup);
|
|
533
|
+
currentGroup = [];
|
|
534
|
+
}
|
|
535
|
+
else if (trimmed && !trimmed.startsWith("COMMIT:")) {
|
|
536
|
+
currentGroup.push(trimmed);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
if (currentGroup.length > 0)
|
|
540
|
+
commitGroups.push(currentGroup);
|
|
541
|
+
// Churn rate per file
|
|
542
|
+
const fileChurn = new Map();
|
|
543
|
+
for (const group of commitGroups) {
|
|
544
|
+
for (const file of group) {
|
|
545
|
+
fileChurn.set(file, (fileChurn.get(file) || 0) + 1);
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
// Co-change pairs (files changed together)
|
|
549
|
+
const coChange = new Map();
|
|
550
|
+
for (const group of commitGroups) {
|
|
551
|
+
if (group.length < 2)
|
|
552
|
+
continue;
|
|
553
|
+
for (let i = 0; i < group.length; i++) {
|
|
554
|
+
for (let j = i + 1; j < group.length; j++) {
|
|
555
|
+
const key = [group[i], group[j]].sort().join(" <-> ");
|
|
556
|
+
coChange.set(key, (coChange.get(key) || 0) + 1);
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
// Build risk scores
|
|
561
|
+
const maxChurn = Math.max(...fileChurn.values(), 1);
|
|
562
|
+
const riskScores = [...fileChurn.entries()]
|
|
563
|
+
.map(([file, churn]) => ({
|
|
564
|
+
file,
|
|
565
|
+
churn,
|
|
566
|
+
churnScore: Math.round((churn / maxChurn) * 100),
|
|
567
|
+
riskScore: Math.round((churn / maxChurn) * 100),
|
|
568
|
+
}))
|
|
569
|
+
.sort((a, b) => b.riskScore - a.riskScore)
|
|
570
|
+
.slice(0, topN);
|
|
571
|
+
// Top co-change pairs
|
|
572
|
+
const topCoChange = [...coChange.entries()]
|
|
573
|
+
.filter(([, count]) => count >= 2)
|
|
574
|
+
.sort((a, b) => b[1] - a[1])
|
|
575
|
+
.slice(0, 5);
|
|
576
|
+
const report = [
|
|
577
|
+
`# 🔥 Git Heatmap Risk`,
|
|
578
|
+
`Período: últimos ${days} días | Commits analizados: ${commitGroups.length} | Archivos rastreados: ${fileChurn.size}`,
|
|
579
|
+
``,
|
|
580
|
+
`## 🌡️ Top ${topN} archivos con mayor churn (más cambian = más riesgo):`,
|
|
581
|
+
riskScores.map((r, i) => ` ${i + 1}. ${r.file}\n Cambios: ${r.churn}x | Risk score: ${r.riskScore}/100 ${r.riskScore >= 70 ? "🔥" : r.riskScore >= 40 ? "⚠️" : "🟢"}`).join("\n"),
|
|
582
|
+
``,
|
|
583
|
+
`## 🔗 Acoplamiento oculto (siempre cambian juntos):`,
|
|
584
|
+
topCoChange.length > 0
|
|
585
|
+
? topCoChange.map(([pair, count]) => ` ⚡ ${pair}\n Co-editados ${count} veces — posible acoplamiento implícito`).join("\n")
|
|
586
|
+
: " Ningún patrón de co-edición significativo detectado.",
|
|
587
|
+
].join("\n");
|
|
588
|
+
return { content: [{ type: "text", text: report }] };
|
|
589
|
+
}
|
|
590
|
+
catch (err) {
|
|
591
|
+
logError("git_heatmap_risk", err);
|
|
592
|
+
return { content: [{ type: "text", text: `Git heatmap failed: ${err.message}` }] };
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
// ─── precrime_static ──────────────────────────────────────────────────────────
|
|
596
|
+
async function handlePrecrimeStatic(args) {
|
|
597
|
+
const dirPath = args?.dirPath || "src";
|
|
598
|
+
const days = args?.days || 30;
|
|
599
|
+
const topN = args?.topN || 10;
|
|
600
|
+
const workspaceRoot = process.cwd();
|
|
601
|
+
try {
|
|
602
|
+
// 1. Run entropy analysis
|
|
603
|
+
const { resolved } = safeResolvePath(workspaceRoot, dirPath);
|
|
604
|
+
const allFiles = [];
|
|
605
|
+
function walkTs(dir, rel) {
|
|
606
|
+
try {
|
|
607
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
608
|
+
for (const e of entries) {
|
|
609
|
+
if (["node_modules", ".git", "dist", "chroma_data", ".lemma"].includes(e.name))
|
|
610
|
+
continue;
|
|
611
|
+
const abs = path.join(dir, e.name);
|
|
612
|
+
const relPath2 = rel ? path.join(rel, e.name).replace(/\\/g, "/") : e.name;
|
|
613
|
+
if (e.isDirectory())
|
|
614
|
+
walkTs(abs, relPath2);
|
|
615
|
+
else if (e.name.match(/\.(ts|tsx)$/) && !e.name.endsWith(".d.ts")) {
|
|
616
|
+
allFiles.push({ abs, rel: relPath2 });
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
catch { }
|
|
621
|
+
}
|
|
622
|
+
walkTs(resolved, dirPath);
|
|
623
|
+
const entropyResults = allFiles
|
|
624
|
+
.map(f => analyzeFileEntropy(f.abs, f.rel))
|
|
625
|
+
.filter((r) => r !== null);
|
|
626
|
+
const entropyMap = new Map(entropyResults.map(r => [r.file, r.entropyScore]));
|
|
627
|
+
// 2. Git churn
|
|
628
|
+
const churnMap = new Map();
|
|
629
|
+
try {
|
|
630
|
+
const since = new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString().split("T")[0];
|
|
631
|
+
const gitLog = execSync(`git log --since="${since}" --name-only --pretty=format:"COMMIT:%H" --diff-filter=M`, { cwd: workspaceRoot, encoding: "utf8", timeout: 10000 });
|
|
632
|
+
for (const line of gitLog.split("\n")) {
|
|
633
|
+
const t = line.trim();
|
|
634
|
+
if (t && !t.startsWith("COMMIT:")) {
|
|
635
|
+
churnMap.set(t, (churnMap.get(t) || 0) + 1);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
catch { }
|
|
640
|
+
const maxChurn = Math.max(...churnMap.values(), 1);
|
|
641
|
+
// 3. Coupling fan-in
|
|
642
|
+
const fanInMap = new Map();
|
|
643
|
+
for (const f of allFiles) {
|
|
644
|
+
const imports = extractImports(f.abs, f.rel, workspaceRoot);
|
|
645
|
+
for (const imp of imports) {
|
|
646
|
+
fanInMap.set(imp, (fanInMap.get(imp) || 0) + 1);
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
const maxFanIn = Math.max(...fanInMap.values(), 1);
|
|
650
|
+
const precrimeResults = allFiles.map(f => {
|
|
651
|
+
const entropy = entropyMap.get(f.rel) || 0;
|
|
652
|
+
const churn = churnMap.get(f.rel) || 0;
|
|
653
|
+
const fanIn = fanInMap.get(f.rel) || 0;
|
|
654
|
+
const churnScore = Math.round((churn / maxChurn) * 100);
|
|
655
|
+
const couplingScore = Math.round((fanIn / maxFanIn) * 100);
|
|
656
|
+
// Weighted: entropy 40%, churn 40%, coupling 20%
|
|
657
|
+
const precrimeScore = Math.round(entropy * 0.4 + churnScore * 0.4 + couplingScore * 0.2);
|
|
658
|
+
const entropyResult = entropyResults.find(r => r.file === f.rel);
|
|
659
|
+
const topRiskyFunctions = entropyResult?.functions
|
|
660
|
+
.filter(fn => fn.cyclomaticComplexity >= 5)
|
|
661
|
+
.slice(0, 3)
|
|
662
|
+
.map(fn => `${fn.name}(CC:${fn.cyclomaticComplexity}, ${fn.lines}L)`) || [];
|
|
663
|
+
const verdict = precrimeScore >= 75 ? "🚨 ARRESTO PREVENTIVO — Alto riesgo de bug" :
|
|
664
|
+
precrimeScore >= 50 ? "⚠️ SOSPECHOSO — Revisar antes de merge" :
|
|
665
|
+
precrimeScore >= 25 ? "🟡 VIGILANCIA — Bajo radar" :
|
|
666
|
+
"✅ LIMPIO";
|
|
667
|
+
return { file: f.rel, precrimeScore, entropyScore: entropy, churnScore, couplingScore, verdict, topRiskyFunctions };
|
|
668
|
+
});
|
|
669
|
+
const topThreats = precrimeResults
|
|
670
|
+
.sort((a, b) => b.precrimeScore - a.precrimeScore)
|
|
671
|
+
.slice(0, topN);
|
|
672
|
+
const report = [
|
|
673
|
+
`# 🔮 PreCrime Static — Minority Report para tu Código`,
|
|
674
|
+
`Archivos bajo análisis: ${allFiles.length} | Período git: ${days}d | Factores: Entropía 40% + Churn 40% + Acoplamiento 20%`,
|
|
675
|
+
``,
|
|
676
|
+
`## 🚨 Predicciones de riesgo:`,
|
|
677
|
+
``,
|
|
678
|
+
...topThreats.map((r, i) => [
|
|
679
|
+
`### ${i + 1}. ${r.file}`,
|
|
680
|
+
`**PreCrime Score: ${r.precrimeScore}/100** — ${r.verdict}`,
|
|
681
|
+
`| Factor | Score |`,
|
|
682
|
+
`|--------|-------|`,
|
|
683
|
+
`| 🧮 Entropía (40%) | ${r.entropyScore}/100 |`,
|
|
684
|
+
`| 🔥 Churn git (40%) | ${r.churnScore}/100 |`,
|
|
685
|
+
`| 🕸️ Acoplamiento (20%) | ${r.couplingScore}/100 |`,
|
|
686
|
+
r.topRiskyFunctions.length > 0
|
|
687
|
+
? `\n**Funciones sospechosas:** ${r.topRiskyFunctions.join(", ")}`
|
|
688
|
+
: "",
|
|
689
|
+
].join("\n")),
|
|
690
|
+
].join("\n");
|
|
691
|
+
return { content: [{ type: "text", text: report }] };
|
|
692
|
+
}
|
|
693
|
+
catch (err) {
|
|
694
|
+
logError("precrime_static", err);
|
|
695
|
+
return { content: [{ type: "text", text: `PreCrime static failed: ${err.message}` }] };
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
// ─── semantic_dedup_guard ─────────────────────────────────────────────────────
|
|
699
|
+
async function handleSemanticDedupGuard(args) {
|
|
700
|
+
const query = args?.query;
|
|
701
|
+
const response = args?.response;
|
|
702
|
+
const threshold = typeof args?.threshold === "number" ? args.threshold : 0.92;
|
|
703
|
+
if (!query || !response)
|
|
704
|
+
throw new Error("query and response are required");
|
|
705
|
+
try {
|
|
706
|
+
const brain = getBrain();
|
|
707
|
+
// A dedup check is not a question the user asked — it must not register as a cache hit.
|
|
708
|
+
const results = brain.search(query, 3, 0, { countStats: false, projectId: deriveProjectId() });
|
|
709
|
+
const topHit = results[0];
|
|
710
|
+
if (!topHit) {
|
|
711
|
+
return {
|
|
712
|
+
content: [{ type: "text", text: JSON.stringify({
|
|
713
|
+
verdict: "STORE_ALLOWED",
|
|
714
|
+
reason: "The Brain está vacío para esta query. Procede con store_memory.",
|
|
715
|
+
similarity: 0,
|
|
716
|
+
}, null, 2) }],
|
|
717
|
+
};
|
|
718
|
+
}
|
|
719
|
+
let sim = topHit.similarity || 0;
|
|
720
|
+
if (topHit.query.trim().toLowerCase() === query.trim().toLowerCase()) {
|
|
721
|
+
sim = 1.0;
|
|
722
|
+
}
|
|
723
|
+
if (sim >= threshold) {
|
|
724
|
+
return {
|
|
725
|
+
content: [{ type: "text", text: JSON.stringify({
|
|
726
|
+
verdict: "DUPLICATE_REJECTED",
|
|
727
|
+
reason: `Duplicado exacto detectado (${(sim * 100).toFixed(1)}% ≥ ${(threshold * 100).toFixed(0)}%). NO hagas store_memory — ya existe.`,
|
|
728
|
+
similarity: sim,
|
|
729
|
+
existingQuery: typeof topHit.query === "string" ? topHit.query.substring(0, 200) : "N/A",
|
|
730
|
+
existingResponse: (topHit.response || "").substring(0, 300),
|
|
731
|
+
tokensSaved: Math.floor(response.length / 4),
|
|
732
|
+
}, null, 2) }],
|
|
733
|
+
};
|
|
734
|
+
}
|
|
735
|
+
if (sim >= 0.75) {
|
|
736
|
+
return {
|
|
737
|
+
content: [{ type: "text", text: JSON.stringify({
|
|
738
|
+
verdict: "SIMILAR_WARNING",
|
|
739
|
+
reason: `Contenido similar detectado (${(sim * 100).toFixed(1)}%). Decide si es distinto suficiente para guardar.`,
|
|
740
|
+
similarity: sim,
|
|
741
|
+
existingQuery: typeof topHit.query === "string" ? topHit.query.substring(0, 200) : "N/A",
|
|
742
|
+
recommendation: "Si aporta info nueva → procede con store_memory. Si es redundante → descarta.",
|
|
743
|
+
}, null, 2) }],
|
|
744
|
+
};
|
|
745
|
+
}
|
|
746
|
+
return {
|
|
747
|
+
content: [{ type: "text", text: JSON.stringify({
|
|
748
|
+
verdict: "STORE_ALLOWED",
|
|
749
|
+
reason: `Contenido único (${(sim * 100).toFixed(1)}% de similitud con lo más cercano). Procede con store_memory.`,
|
|
750
|
+
similarity: sim,
|
|
751
|
+
}, null, 2) }],
|
|
752
|
+
};
|
|
753
|
+
}
|
|
754
|
+
catch (e) {
|
|
755
|
+
logError("semantic_dedup_guard", e);
|
|
756
|
+
return {
|
|
757
|
+
content: [{ type: "text", text: JSON.stringify({
|
|
758
|
+
verdict: "STORE_ALLOWED",
|
|
759
|
+
reason: "El cache local del brain falló. Procediendo con store.",
|
|
760
|
+
error: e.message,
|
|
761
|
+
}, null, 2) }],
|
|
762
|
+
};
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
// ─── dead_export_necromancer ──────────────────────────────────────────────────
|
|
766
|
+
async function handleDeadExportNecromancer(args) {
|
|
767
|
+
const dirPath = args?.dirPath || "src";
|
|
768
|
+
const workspaceRoot = process.cwd();
|
|
769
|
+
try {
|
|
770
|
+
const { resolved } = safeResolvePath(workspaceRoot, dirPath);
|
|
771
|
+
const allFiles = [];
|
|
772
|
+
function walkForNecro(dir, rel) {
|
|
773
|
+
try {
|
|
774
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
775
|
+
for (const e of entries) {
|
|
776
|
+
if (["node_modules", ".git", "dist", "chroma_data", ".lemma"].includes(e.name))
|
|
777
|
+
continue;
|
|
778
|
+
const abs = path.join(dir, e.name);
|
|
779
|
+
const relPath2 = rel ? path.join(rel, e.name).replace(/\\/g, "/") : e.name;
|
|
780
|
+
if (e.isDirectory())
|
|
781
|
+
walkForNecro(abs, relPath2);
|
|
782
|
+
else if (e.name.match(/\.(ts|tsx)$/) && !e.name.endsWith(".d.ts")) {
|
|
783
|
+
allFiles.push({ abs, rel: relPath2 });
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
catch { }
|
|
788
|
+
}
|
|
789
|
+
walkForNecro(resolved, dirPath);
|
|
790
|
+
const allExports = [];
|
|
791
|
+
for (const f of allFiles) {
|
|
792
|
+
try {
|
|
793
|
+
const src = fs.readFileSync(f.abs, "utf8");
|
|
794
|
+
const sourceFile = ts.createSourceFile(f.abs, src, ts.ScriptTarget.Latest, true);
|
|
795
|
+
function visitExports(node) {
|
|
796
|
+
const mods = ts.canHaveModifiers(node) ? ts.getModifiers(node) : undefined;
|
|
797
|
+
const isExported = mods?.some(m => m.kind === ts.SyntaxKind.ExportKeyword);
|
|
798
|
+
if (isExported) {
|
|
799
|
+
const line = sourceFile.getLineAndCharacterOfPosition(node.getStart()).line + 1;
|
|
800
|
+
if (ts.isFunctionDeclaration(node) && node.name) {
|
|
801
|
+
allExports.push({ file: f.rel, name: node.name.text, line, kind: "function" });
|
|
802
|
+
}
|
|
803
|
+
else if (ts.isClassDeclaration(node) && node.name) {
|
|
804
|
+
allExports.push({ file: f.rel, name: node.name.text, line, kind: "class" });
|
|
805
|
+
}
|
|
806
|
+
else if (ts.isInterfaceDeclaration(node) && node.name) {
|
|
807
|
+
allExports.push({ file: f.rel, name: node.name.text, line, kind: "interface" });
|
|
808
|
+
}
|
|
809
|
+
else if (ts.isTypeAliasDeclaration(node) && node.name) {
|
|
810
|
+
allExports.push({ file: f.rel, name: node.name.text, line, kind: "type" });
|
|
811
|
+
}
|
|
812
|
+
else if (ts.isVariableStatement(node)) {
|
|
813
|
+
for (const decl of node.declarationList.declarations) {
|
|
814
|
+
if (ts.isIdentifier(decl.name)) {
|
|
815
|
+
allExports.push({ file: f.rel, name: decl.name.text, line, kind: "const" });
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
ts.forEachChild(node, visitExports);
|
|
821
|
+
}
|
|
822
|
+
visitExports(sourceFile);
|
|
823
|
+
}
|
|
824
|
+
catch { }
|
|
825
|
+
}
|
|
826
|
+
// Collect all imported names across the workspace
|
|
827
|
+
const importedNames = new Set();
|
|
828
|
+
for (const f of allFiles) {
|
|
829
|
+
try {
|
|
830
|
+
const src = fs.readFileSync(f.abs, "utf8");
|
|
831
|
+
const sourceFile = ts.createSourceFile(f.abs, src, ts.ScriptTarget.Latest, true);
|
|
832
|
+
ts.forEachChild(sourceFile, node => {
|
|
833
|
+
if (ts.isImportDeclaration(node) && node.importClause) {
|
|
834
|
+
const clause = node.importClause;
|
|
835
|
+
if (clause.name)
|
|
836
|
+
importedNames.add(clause.name.text); // default import
|
|
837
|
+
if (clause.namedBindings && ts.isNamedImports(clause.namedBindings)) {
|
|
838
|
+
for (const spec of clause.namedBindings.elements) {
|
|
839
|
+
importedNames.add(spec.name.text);
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
});
|
|
844
|
+
}
|
|
845
|
+
catch { }
|
|
846
|
+
}
|
|
847
|
+
// Find dead exports
|
|
848
|
+
const deadExports = allExports.filter(e => !importedNames.has(e.name));
|
|
849
|
+
// Estimate tokens wasted (approximate: name + type annotation + function body avg ~50 tokens per export)
|
|
850
|
+
const estimatedWastedTokens = deadExports.length * 50;
|
|
851
|
+
if (deadExports.length === 0) {
|
|
852
|
+
return {
|
|
853
|
+
content: [{ type: "text", text: "✅ No se encontraron exports muertos. Cada export tiene al menos un importador." }],
|
|
854
|
+
};
|
|
855
|
+
}
|
|
856
|
+
// Group by file
|
|
857
|
+
const byFile = new Map();
|
|
858
|
+
for (const e of deadExports) {
|
|
859
|
+
if (!byFile.has(e.file))
|
|
860
|
+
byFile.set(e.file, []);
|
|
861
|
+
byFile.get(e.file).push(e);
|
|
862
|
+
}
|
|
863
|
+
const report = [
|
|
864
|
+
`# 💀 Dead Export Necromancer`,
|
|
865
|
+
`Exports totales: ${allExports.length} | Exports muertos: ${deadExports.length} | ~${estimatedWastedTokens} tokens desperdiciados en contexto`,
|
|
866
|
+
``,
|
|
867
|
+
`## 🪦 Código zombie por archivo:`,
|
|
868
|
+
[...byFile.entries()]
|
|
869
|
+
.sort((a, b) => b[1].length - a[1].length)
|
|
870
|
+
.map(([file, exports]) => `### ${file} (${exports.length} exports muertos)\n` +
|
|
871
|
+
exports.map(e => ` 💀 \`${e.name}\` [${e.kind}] — línea ${e.line}`).join("\n"))
|
|
872
|
+
.join("\n\n"),
|
|
873
|
+
``,
|
|
874
|
+
`## 💡 Recomendación:`,
|
|
875
|
+
`Eliminar estos ${deadExports.length} exports muertos liberaría ~${estimatedWastedTokens} tokens de contexto por sesión.`,
|
|
876
|
+
`Verifica que no sean parte de la API pública del paquete antes de eliminar.`,
|
|
877
|
+
].join("\n");
|
|
878
|
+
return { content: [{ type: "text", text: report }] };
|
|
879
|
+
}
|
|
880
|
+
catch (err) {
|
|
881
|
+
logError("dead_export_necromancer", err);
|
|
882
|
+
return { content: [{ type: "text", text: `Dead export necromancer failed: ${err.message}` }] };
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
export const ANALYSIS_TOOL_HANDLERS = {
|
|
886
|
+
entropy_score: handleEntropyScore,
|
|
887
|
+
coupling_radar: handleCouplingRadar,
|
|
888
|
+
pattern_fossil: handlePatternFossil,
|
|
889
|
+
git_heatmap_risk: handleGitHeatmapRisk,
|
|
890
|
+
precrime_static: handlePrecrimeStatic,
|
|
891
|
+
semantic_dedup_guard: handleSemanticDedupGuard,
|
|
892
|
+
dead_export_necromancer: handleDeadExportNecromancer,
|
|
893
|
+
};
|
|
894
|
+
//# sourceMappingURL=analysis.js.map
|