@hongmaple0820/med-scale-research-os 0.43.0
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/.scale/mcp-servers.yaml +144 -0
- package/.scale/skills.json +830 -0
- package/.scale/verification.json +52 -0
- package/LICENSE +15 -0
- package/README.en.md +156 -0
- package/README.md +156 -0
- package/dist/adapters/AiderAdapter.d.ts +22 -0
- package/dist/adapters/AiderAdapter.js +262 -0
- package/dist/adapters/AiderAdapter.js.map +1 -0
- package/dist/adapters/AntigravityAdapter.d.ts +4 -0
- package/dist/adapters/AntigravityAdapter.js +21 -0
- package/dist/adapters/AntigravityAdapter.js.map +1 -0
- package/dist/adapters/ClaudeCodeAdapter.d.ts +54 -0
- package/dist/adapters/ClaudeCodeAdapter.js +185 -0
- package/dist/adapters/ClaudeCodeAdapter.js.map +1 -0
- package/dist/adapters/ClineAdapter.d.ts +4 -0
- package/dist/adapters/ClineAdapter.js +20 -0
- package/dist/adapters/ClineAdapter.js.map +1 -0
- package/dist/adapters/CodexAdapter.d.ts +15 -0
- package/dist/adapters/CodexAdapter.js +160 -0
- package/dist/adapters/CodexAdapter.js.map +1 -0
- package/dist/adapters/CursorAdapter.d.ts +14 -0
- package/dist/adapters/CursorAdapter.js +171 -0
- package/dist/adapters/CursorAdapter.js.map +1 -0
- package/dist/adapters/DeepSeekTuiAdapter.d.ts +19 -0
- package/dist/adapters/DeepSeekTuiAdapter.js +263 -0
- package/dist/adapters/DeepSeekTuiAdapter.js.map +1 -0
- package/dist/adapters/DoubaoAdapter.d.ts +14 -0
- package/dist/adapters/DoubaoAdapter.js +184 -0
- package/dist/adapters/DoubaoAdapter.js.map +1 -0
- package/dist/adapters/GeminiAdapter.d.ts +14 -0
- package/dist/adapters/GeminiAdapter.js +163 -0
- package/dist/adapters/GeminiAdapter.js.map +1 -0
- package/dist/adapters/GenericProjectAgentAdapter.d.ts +29 -0
- package/dist/adapters/GenericProjectAgentAdapter.js +204 -0
- package/dist/adapters/GenericProjectAgentAdapter.js.map +1 -0
- package/dist/adapters/HermesAdapter.d.ts +14 -0
- package/dist/adapters/HermesAdapter.js +163 -0
- package/dist/adapters/HermesAdapter.js.map +1 -0
- package/dist/adapters/JCodeAdapter.d.ts +4 -0
- package/dist/adapters/JCodeAdapter.js +19 -0
- package/dist/adapters/JCodeAdapter.js.map +1 -0
- package/dist/adapters/KiloCodeAdapter.d.ts +4 -0
- package/dist/adapters/KiloCodeAdapter.js +20 -0
- package/dist/adapters/KiloCodeAdapter.js.map +1 -0
- package/dist/adapters/KimiAdapter.d.ts +14 -0
- package/dist/adapters/KimiAdapter.js +183 -0
- package/dist/adapters/KimiAdapter.js.map +1 -0
- package/dist/adapters/KiroAdapter.d.ts +14 -0
- package/dist/adapters/KiroAdapter.js +180 -0
- package/dist/adapters/KiroAdapter.js.map +1 -0
- package/dist/adapters/OpenClawAdapter.d.ts +14 -0
- package/dist/adapters/OpenClawAdapter.js +163 -0
- package/dist/adapters/OpenClawAdapter.js.map +1 -0
- package/dist/adapters/OpenCodeAdapter.d.ts +14 -0
- package/dist/adapters/OpenCodeAdapter.js +172 -0
- package/dist/adapters/OpenCodeAdapter.js.map +1 -0
- package/dist/adapters/QCoderAdapter.d.ts +14 -0
- package/dist/adapters/QCoderAdapter.js +159 -0
- package/dist/adapters/QCoderAdapter.js.map +1 -0
- package/dist/adapters/QoderAdapter.d.ts +4 -0
- package/dist/adapters/QoderAdapter.js +21 -0
- package/dist/adapters/QoderAdapter.js.map +1 -0
- package/dist/adapters/TraeAdapter.d.ts +14 -0
- package/dist/adapters/TraeAdapter.js +159 -0
- package/dist/adapters/TraeAdapter.js.map +1 -0
- package/dist/adapters/VSCAdapter.d.ts +14 -0
- package/dist/adapters/VSCAdapter.js +159 -0
- package/dist/adapters/VSCAdapter.js.map +1 -0
- package/dist/adapters/WindsurfAdapter.d.ts +14 -0
- package/dist/adapters/WindsurfAdapter.js +185 -0
- package/dist/adapters/WindsurfAdapter.js.map +1 -0
- package/dist/adapters/WorkBuddyAdapter.d.ts +14 -0
- package/dist/adapters/WorkBuddyAdapter.js +159 -0
- package/dist/adapters/WorkBuddyAdapter.js.map +1 -0
- package/dist/adapters/index.d.ts +32 -0
- package/dist/adapters/index.js +87 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/agents/AgentChannel.d.ts +43 -0
- package/dist/agents/AgentChannel.js +136 -0
- package/dist/agents/AgentChannel.js.map +1 -0
- package/dist/agents/AgentCoordinator.d.ts +29 -0
- package/dist/agents/AgentCoordinator.js +136 -0
- package/dist/agents/AgentCoordinator.js.map +1 -0
- package/dist/agents/AgentDispatcher.d.ts +24 -0
- package/dist/agents/AgentDispatcher.js +112 -0
- package/dist/agents/AgentDispatcher.js.map +1 -0
- package/dist/agents/AgentManager.d.ts +14 -0
- package/dist/agents/AgentManager.js +85 -0
- package/dist/agents/AgentManager.js.map +1 -0
- package/dist/agents/AgentPool.d.ts +59 -0
- package/dist/agents/AgentPool.js +192 -0
- package/dist/agents/AgentPool.js.map +1 -0
- package/dist/agents/AgentRegistry.d.ts +20 -0
- package/dist/agents/AgentRegistry.js +36 -0
- package/dist/agents/AgentRegistry.js.map +1 -0
- package/dist/agents/AgentSourceLoader.d.ts +73 -0
- package/dist/agents/AgentSourceLoader.js +103 -0
- package/dist/agents/AgentSourceLoader.js.map +1 -0
- package/dist/agents/IAgent.d.ts +53 -0
- package/dist/agents/IAgent.js +4 -0
- package/dist/agents/IAgent.js.map +1 -0
- package/dist/agents/LeadershipPresets.d.ts +16 -0
- package/dist/agents/LeadershipPresets.js +152 -0
- package/dist/agents/LeadershipPresets.js.map +1 -0
- package/dist/agents/definitions/debugger.d.ts +2 -0
- package/dist/agents/definitions/debugger.js +6 -0
- package/dist/agents/definitions/debugger.js.map +1 -0
- package/dist/agents/definitions/doc-writer.d.ts +2 -0
- package/dist/agents/definitions/doc-writer.js +6 -0
- package/dist/agents/definitions/doc-writer.js.map +1 -0
- package/dist/agents/definitions/implementer.d.ts +2 -0
- package/dist/agents/definitions/implementer.js +6 -0
- package/dist/agents/definitions/implementer.js.map +1 -0
- package/dist/agents/definitions/planner.d.ts +2 -0
- package/dist/agents/definitions/planner.js +6 -0
- package/dist/agents/definitions/planner.js.map +1 -0
- package/dist/agents/definitions/researcher.d.ts +2 -0
- package/dist/agents/definitions/researcher.js +6 -0
- package/dist/agents/definitions/researcher.js.map +1 -0
- package/dist/agents/definitions/reviewer.d.ts +2 -0
- package/dist/agents/definitions/reviewer.js +6 -0
- package/dist/agents/definitions/reviewer.js.map +1 -0
- package/dist/agents/definitions/security.d.ts +2 -0
- package/dist/agents/definitions/security.js +6 -0
- package/dist/agents/definitions/security.js.map +1 -0
- package/dist/agents/definitions/tester.d.ts +2 -0
- package/dist/agents/definitions/tester.js +6 -0
- package/dist/agents/definitions/tester.js.map +1 -0
- package/dist/agents/index.d.ts +23 -0
- package/dist/agents/index.js +44 -0
- package/dist/agents/index.js.map +1 -0
- package/dist/agents/profiles.d.ts +26 -0
- package/dist/agents/profiles.js +197 -0
- package/dist/agents/profiles.js.map +1 -0
- package/dist/agents/types.d.ts +262 -0
- package/dist/agents/types.js +4 -0
- package/dist/agents/types.js.map +1 -0
- package/dist/api/cli.d.ts +2 -0
- package/dist/api/cli.js +6678 -0
- package/dist/api/cli.js.map +1 -0
- package/dist/api/doctor.d.ts +83 -0
- package/dist/api/doctor.js +982 -0
- package/dist/api/doctor.js.map +1 -0
- package/dist/api/mcp.d.ts +32 -0
- package/dist/api/mcp.js +223 -0
- package/dist/api/mcp.js.map +1 -0
- package/dist/api/medscale.d.ts +2 -0
- package/dist/api/medscale.js +20 -0
- package/dist/api/medscale.js.map +1 -0
- package/dist/api/quickstart.d.ts +86 -0
- package/dist/api/quickstart.js +291 -0
- package/dist/api/quickstart.js.map +1 -0
- package/dist/artifact/fsm.d.ts +41 -0
- package/dist/artifact/fsm.js +221 -0
- package/dist/artifact/fsm.js.map +1 -0
- package/dist/artifact/fsmDefinitions.d.ts +18 -0
- package/dist/artifact/fsmDefinitions.js +296 -0
- package/dist/artifact/fsmDefinitions.js.map +1 -0
- package/dist/artifact/sqliteStore.d.ts +61 -0
- package/dist/artifact/sqliteStore.js +381 -0
- package/dist/artifact/sqliteStore.js.map +1 -0
- package/dist/artifact/store.d.ts +49 -0
- package/dist/artifact/store.js +116 -0
- package/dist/artifact/store.js.map +1 -0
- package/dist/artifact/types.d.ts +535 -0
- package/dist/artifact/types.js +74 -0
- package/dist/artifact/types.js.map +1 -0
- package/dist/bootstrap/DependencyBootstrap.d.ts +112 -0
- package/dist/bootstrap/DependencyBootstrap.js +1046 -0
- package/dist/bootstrap/DependencyBootstrap.js.map +1 -0
- package/dist/bootstrap/DependencyBootstrapRenderer.d.ts +3 -0
- package/dist/bootstrap/DependencyBootstrapRenderer.js +138 -0
- package/dist/bootstrap/DependencyBootstrapRenderer.js.map +1 -0
- package/dist/bridge/PythonBridge.d.ts +80 -0
- package/dist/bridge/PythonBridge.js +437 -0
- package/dist/bridge/PythonBridge.js.map +1 -0
- package/dist/bridge/index.d.ts +2 -0
- package/dist/bridge/index.js +7 -0
- package/dist/bridge/index.js.map +1 -0
- package/dist/bridge/medicalWorkflows.d.ts +29 -0
- package/dist/bridge/medicalWorkflows.js +156 -0
- package/dist/bridge/medicalWorkflows.js.map +1 -0
- package/dist/bridge/types.d.ts +381 -0
- package/dist/bridge/types.js +113 -0
- package/dist/bridge/types.js.map +1 -0
- package/dist/cache/ScanCache.d.ts +41 -0
- package/dist/cache/ScanCache.js +120 -0
- package/dist/cache/ScanCache.js.map +1 -0
- package/dist/capabilities/BrowserCapability.d.ts +30 -0
- package/dist/capabilities/BrowserCapability.js +73 -0
- package/dist/capabilities/BrowserCapability.js.map +1 -0
- package/dist/capabilities/BrowserQACapability.d.ts +165 -0
- package/dist/capabilities/BrowserQACapability.js +438 -0
- package/dist/capabilities/BrowserQACapability.js.map +1 -0
- package/dist/capabilities/CapabilityRegistry.d.ts +17 -0
- package/dist/capabilities/CapabilityRegistry.js +65 -0
- package/dist/capabilities/CapabilityRegistry.js.map +1 -0
- package/dist/capabilities/ComputerCapability.d.ts +28 -0
- package/dist/capabilities/ComputerCapability.js +40 -0
- package/dist/capabilities/ComputerCapability.js.map +1 -0
- package/dist/capabilities/InstalledSkillsIntegration.d.ts +69 -0
- package/dist/capabilities/InstalledSkillsIntegration.js +240 -0
- package/dist/capabilities/InstalledSkillsIntegration.js.map +1 -0
- package/dist/capabilities/SearchCapability.d.ts +46 -0
- package/dist/capabilities/SearchCapability.js +88 -0
- package/dist/capabilities/SearchCapability.js.map +1 -0
- package/dist/capabilities/index.d.ts +6 -0
- package/dist/capabilities/index.js +9 -0
- package/dist/capabilities/index.js.map +1 -0
- package/dist/capabilities/types.d.ts +92 -0
- package/dist/capabilities/types.js +7 -0
- package/dist/capabilities/types.js.map +1 -0
- package/dist/cli/autofixCommands.d.ts +22 -0
- package/dist/cli/autofixCommands.js +32 -0
- package/dist/cli/autofixCommands.js.map +1 -0
- package/dist/cli/cortexCommands.d.ts +71 -0
- package/dist/cli/cortexCommands.js +335 -0
- package/dist/cli/cortexCommands.js.map +1 -0
- package/dist/cli/costCommands.d.ts +13 -0
- package/dist/cli/costCommands.js +48 -0
- package/dist/cli/costCommands.js.map +1 -0
- package/dist/cli/evolutionCommands.d.ts +112 -0
- package/dist/cli/evolutionCommands.js +246 -0
- package/dist/cli/evolutionCommands.js.map +1 -0
- package/dist/cli/gateStatusCommands.d.ts +1 -0
- package/dist/cli/gateStatusCommands.js +52 -0
- package/dist/cli/gateStatusCommands.js.map +1 -0
- package/dist/cli/liteCommands.d.ts +81 -0
- package/dist/cli/liteCommands.js +148 -0
- package/dist/cli/liteCommands.js.map +1 -0
- package/dist/cli/orchCommands.d.ts +43 -0
- package/dist/cli/orchCommands.js +135 -0
- package/dist/cli/orchCommands.js.map +1 -0
- package/dist/cli/phaseCommands.d.ts +248 -0
- package/dist/cli/phaseCommands.js +1878 -0
- package/dist/cli/phaseCommands.js.map +1 -0
- package/dist/cli/promptCommands.d.ts +1 -0
- package/dist/cli/promptCommands.js +57 -0
- package/dist/cli/promptCommands.js.map +1 -0
- package/dist/cli/qaCommands.d.ts +22 -0
- package/dist/cli/qaCommands.js +84 -0
- package/dist/cli/qaCommands.js.map +1 -0
- package/dist/cli/quickstartCommands.d.ts +17 -0
- package/dist/cli/quickstartCommands.js +47 -0
- package/dist/cli/quickstartCommands.js.map +1 -0
- package/dist/cli/runCommand.d.ts +39 -0
- package/dist/cli/runCommand.js +113 -0
- package/dist/cli/runCommand.js.map +1 -0
- package/dist/cli/scoreCommands.d.ts +1 -0
- package/dist/cli/scoreCommands.js +112 -0
- package/dist/cli/scoreCommands.js.map +1 -0
- package/dist/cli/shieldCommands.d.ts +30 -0
- package/dist/cli/shieldCommands.js +212 -0
- package/dist/cli/shieldCommands.js.map +1 -0
- package/dist/cli/targetCommands.d.ts +552 -0
- package/dist/cli/targetCommands.js +3173 -0
- package/dist/cli/targetCommands.js.map +1 -0
- package/dist/cli/tuiCommands.d.ts +7 -0
- package/dist/cli/tuiCommands.js +33 -0
- package/dist/cli/tuiCommands.js.map +1 -0
- package/dist/cli/vibeCommands.d.ts +64 -0
- package/dist/cli/vibeCommands.js +221 -0
- package/dist/cli/vibeCommands.js.map +1 -0
- package/dist/codegraph/CodeIntelligence.d.ts +147 -0
- package/dist/codegraph/CodeIntelligence.js +681 -0
- package/dist/codegraph/CodeIntelligence.js.map +1 -0
- package/dist/config/profiles.d.ts +64 -0
- package/dist/config/profiles.js +223 -0
- package/dist/config/profiles.js.map +1 -0
- package/dist/context/AntiPatternRegistry.d.ts +38 -0
- package/dist/context/AntiPatternRegistry.js +203 -0
- package/dist/context/AntiPatternRegistry.js.map +1 -0
- package/dist/context/CavemanCompressor.d.ts +20 -0
- package/dist/context/CavemanCompressor.js +14 -0
- package/dist/context/CavemanCompressor.js.map +1 -0
- package/dist/context/ContextBudget.d.ts +128 -0
- package/dist/context/ContextBudget.js +423 -0
- package/dist/context/ContextBudget.js.map +1 -0
- package/dist/context/ContextBuilder.d.ts +71 -0
- package/dist/context/ContextBuilder.js +372 -0
- package/dist/context/ContextBuilder.js.map +1 -0
- package/dist/context/ContextCompiler.d.ts +34 -0
- package/dist/context/ContextCompiler.js +120 -0
- package/dist/context/ContextCompiler.js.map +1 -0
- package/dist/context/ProjectAnatomy.d.ts +18 -0
- package/dist/context/ProjectAnatomy.js +287 -0
- package/dist/context/ProjectAnatomy.js.map +1 -0
- package/dist/context/SessionStartSequence.d.ts +54 -0
- package/dist/context/SessionStartSequence.js +162 -0
- package/dist/context/SessionStartSequence.js.map +1 -0
- package/dist/core/ExternalCommand.d.ts +9 -0
- package/dist/core/ExternalCommand.js +70 -0
- package/dist/core/ExternalCommand.js.map +1 -0
- package/dist/core/GbrainRuntime.d.ts +25 -0
- package/dist/core/GbrainRuntime.js +270 -0
- package/dist/core/GbrainRuntime.js.map +1 -0
- package/dist/core/container.d.ts +14 -0
- package/dist/core/container.js +35 -0
- package/dist/core/container.js.map +1 -0
- package/dist/core/eventBus.d.ts +60 -0
- package/dist/core/eventBus.js +157 -0
- package/dist/core/eventBus.js.map +1 -0
- package/dist/core/logger.d.ts +5 -0
- package/dist/core/logger.js +51 -0
- package/dist/core/logger.js.map +1 -0
- package/dist/cortex/GovernanceMetrics.d.ts +66 -0
- package/dist/cortex/GovernanceMetrics.js +230 -0
- package/dist/cortex/GovernanceMetrics.js.map +1 -0
- package/dist/cortex/InstinctExtractor.d.ts +61 -0
- package/dist/cortex/InstinctExtractor.js +184 -0
- package/dist/cortex/InstinctExtractor.js.map +1 -0
- package/dist/cortex/InstinctStore.d.ts +54 -0
- package/dist/cortex/InstinctStore.js +266 -0
- package/dist/cortex/InstinctStore.js.map +1 -0
- package/dist/cortex/ReflexionEngine.d.ts +34 -0
- package/dist/cortex/ReflexionEngine.js +157 -0
- package/dist/cortex/ReflexionEngine.js.map +1 -0
- package/dist/cortex/SessionInjector.d.ts +44 -0
- package/dist/cortex/SessionInjector.js +127 -0
- package/dist/cortex/SessionInjector.js.map +1 -0
- package/dist/cortex/adapters/ClaudeAdapter.d.ts +17 -0
- package/dist/cortex/adapters/ClaudeAdapter.js +61 -0
- package/dist/cortex/adapters/ClaudeAdapter.js.map +1 -0
- package/dist/cortex/adapters/CodexAdapter.d.ts +10 -0
- package/dist/cortex/adapters/CodexAdapter.js +52 -0
- package/dist/cortex/adapters/CodexAdapter.js.map +1 -0
- package/dist/cortex/adapters/CursorAdapter.d.ts +10 -0
- package/dist/cortex/adapters/CursorAdapter.js +46 -0
- package/dist/cortex/adapters/CursorAdapter.js.map +1 -0
- package/dist/cortex/adapters/GeminiAdapter.d.ts +11 -0
- package/dist/cortex/adapters/GeminiAdapter.js +48 -0
- package/dist/cortex/adapters/GeminiAdapter.js.map +1 -0
- package/dist/dashboard/DashboardServer.d.ts +86 -0
- package/dist/dashboard/DashboardServer.js +380 -0
- package/dist/dashboard/DashboardServer.js.map +1 -0
- package/dist/dashboard/MedicalWorkflowData.d.ts +155 -0
- package/dist/dashboard/MedicalWorkflowData.js +664 -0
- package/dist/dashboard/MedicalWorkflowData.js.map +1 -0
- package/dist/dashboard/MetricsAggregator.d.ts +38 -0
- package/dist/dashboard/MetricsAggregator.js +99 -0
- package/dist/dashboard/MetricsAggregator.js.map +1 -0
- package/dist/dashboard/index.d.ts +4 -0
- package/dist/dashboard/index.js +3 -0
- package/dist/dashboard/index.js.map +1 -0
- package/dist/dashboard/server.d.ts +52 -0
- package/dist/dashboard/server.js +84 -0
- package/dist/dashboard/server.js.map +1 -0
- package/dist/env/EnvironmentDoctor.d.ts +66 -0
- package/dist/env/EnvironmentDoctor.js +581 -0
- package/dist/env/EnvironmentDoctor.js.map +1 -0
- package/dist/eval/BenchmarkPublisher.d.ts +25 -0
- package/dist/eval/BenchmarkPublisher.js +27 -0
- package/dist/eval/BenchmarkPublisher.js.map +1 -0
- package/dist/eval/WorkflowEval.d.ts +161 -0
- package/dist/eval/WorkflowEval.js +377 -0
- package/dist/eval/WorkflowEval.js.map +1 -0
- package/dist/evolution/AutoDefectCreator.d.ts +43 -0
- package/dist/evolution/AutoDefectCreator.js +157 -0
- package/dist/evolution/AutoDefectCreator.js.map +1 -0
- package/dist/evolution/BehaviorTracker.d.ts +46 -0
- package/dist/evolution/BehaviorTracker.js +67 -0
- package/dist/evolution/BehaviorTracker.js.map +1 -0
- package/dist/evolution/EvolutionEngine.d.ts +102 -0
- package/dist/evolution/EvolutionEngine.js +326 -0
- package/dist/evolution/EvolutionEngine.js.map +1 -0
- package/dist/evolution/EvolutionEvaluator.d.ts +61 -0
- package/dist/evolution/EvolutionEvaluator.js +118 -0
- package/dist/evolution/EvolutionEvaluator.js.map +1 -0
- package/dist/evolution/LessonValidator.d.ts +36 -0
- package/dist/evolution/LessonValidator.js +132 -0
- package/dist/evolution/LessonValidator.js.map +1 -0
- package/dist/evolution/PatternExtractor.d.ts +40 -0
- package/dist/evolution/PatternExtractor.js +83 -0
- package/dist/evolution/PatternExtractor.js.map +1 -0
- package/dist/evolution/RuleMaturity.d.ts +39 -0
- package/dist/evolution/RuleMaturity.js +70 -0
- package/dist/evolution/RuleMaturity.js.map +1 -0
- package/dist/evolution/SessionLearnings.d.ts +70 -0
- package/dist/evolution/SessionLearnings.js +217 -0
- package/dist/evolution/SessionLearnings.js.map +1 -0
- package/dist/evolution/SkillCreator.d.ts +75 -0
- package/dist/evolution/SkillCreator.js +219 -0
- package/dist/evolution/SkillCreator.js.map +1 -0
- package/dist/fsm/FSMAgentBridge.d.ts +59 -0
- package/dist/fsm/FSMAgentBridge.js +193 -0
- package/dist/fsm/FSMAgentBridge.js.map +1 -0
- package/dist/fsm/index.d.ts +2 -0
- package/dist/fsm/index.js +3 -0
- package/dist/fsm/index.js.map +1 -0
- package/dist/governance/GovernanceRoi.d.ts +30 -0
- package/dist/governance/GovernanceRoi.js +102 -0
- package/dist/governance/GovernanceRoi.js.map +1 -0
- package/dist/governance/ProgressiveGovernance.d.ts +22 -0
- package/dist/governance/ProgressiveGovernance.js +159 -0
- package/dist/governance/ProgressiveGovernance.js.map +1 -0
- package/dist/guardrails/ActiveRedTeam.d.ts +46 -0
- package/dist/guardrails/ActiveRedTeam.js +203 -0
- package/dist/guardrails/ActiveRedTeam.js.map +1 -0
- package/dist/guardrails/DependencyAuditor.d.ts +68 -0
- package/dist/guardrails/DependencyAuditor.js +378 -0
- package/dist/guardrails/DependencyAuditor.js.map +1 -0
- package/dist/guardrails/DetectorEnhanced.d.ts +111 -0
- package/dist/guardrails/DetectorEnhanced.js +202 -0
- package/dist/guardrails/DetectorEnhanced.js.map +1 -0
- package/dist/guardrails/GateEvaluator.d.ts +18 -0
- package/dist/guardrails/GateEvaluator.js +129 -0
- package/dist/guardrails/GateEvaluator.js.map +1 -0
- package/dist/guardrails/Gateway.d.ts +26 -0
- package/dist/guardrails/Gateway.js +56 -0
- package/dist/guardrails/Gateway.js.map +1 -0
- package/dist/guardrails/OWASPDetector.d.ts +58 -0
- package/dist/guardrails/OWASPDetector.js +508 -0
- package/dist/guardrails/OWASPDetector.js.map +1 -0
- package/dist/guardrails/ReviewEnforcer.d.ts +52 -0
- package/dist/guardrails/ReviewEnforcer.js +117 -0
- package/dist/guardrails/ReviewEnforcer.js.map +1 -0
- package/dist/guardrails/advancedDetectors.d.ts +38 -0
- package/dist/guardrails/advancedDetectors.js +188 -0
- package/dist/guardrails/advancedDetectors.js.map +1 -0
- package/dist/guardrails/detectors.d.ts +34 -0
- package/dist/guardrails/detectors.js +332 -0
- package/dist/guardrails/detectors.js.map +1 -0
- package/dist/guardrails/roles.d.ts +4 -0
- package/dist/guardrails/roles.js +54 -0
- package/dist/guardrails/roles.js.map +1 -0
- package/dist/hooks/BugPatternDetector.d.ts +36 -0
- package/dist/hooks/BugPatternDetector.js +207 -0
- package/dist/hooks/BugPatternDetector.js.map +1 -0
- package/dist/hooks/HookDeployer.d.ts +44 -0
- package/dist/hooks/HookDeployer.js +144 -0
- package/dist/hooks/HookDeployer.js.map +1 -0
- package/dist/hooks/HookGeneratorEnhanced.d.ts +67 -0
- package/dist/hooks/HookGeneratorEnhanced.js +641 -0
- package/dist/hooks/HookGeneratorEnhanced.js.map +1 -0
- package/dist/hooks/WorkflowHooksManager.d.ts +30 -0
- package/dist/hooks/WorkflowHooksManager.js +160 -0
- package/dist/hooks/WorkflowHooksManager.js.map +1 -0
- package/dist/hooks/index.d.ts +6 -0
- package/dist/hooks/index.js +5 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/i18n/Language.d.ts +9 -0
- package/dist/i18n/Language.js +38 -0
- package/dist/i18n/Language.js.map +1 -0
- package/dist/index.d.ts +101 -0
- package/dist/index.js +104 -0
- package/dist/index.js.map +1 -0
- package/dist/knowledge/CerebrumManager.d.ts +25 -0
- package/dist/knowledge/CerebrumManager.js +127 -0
- package/dist/knowledge/CerebrumManager.js.map +1 -0
- package/dist/knowledge/GraphifyKnowledgeBase.d.ts +38 -0
- package/dist/knowledge/GraphifyKnowledgeBase.js +409 -0
- package/dist/knowledge/GraphifyKnowledgeBase.js.map +1 -0
- package/dist/knowledge/KnowledgeBase.d.ts +51 -0
- package/dist/knowledge/KnowledgeBase.js +182 -0
- package/dist/knowledge/KnowledgeBase.js.map +1 -0
- package/dist/knowledge/SQLiteKnowledgeBase.d.ts +29 -0
- package/dist/knowledge/SQLiteKnowledgeBase.js +203 -0
- package/dist/knowledge/SQLiteKnowledgeBase.js.map +1 -0
- package/dist/knowledge/TfidfIndex.d.ts +50 -0
- package/dist/knowledge/TfidfIndex.js +177 -0
- package/dist/knowledge/TfidfIndex.js.map +1 -0
- package/dist/knowledge/UbiquitousLanguageManager.d.ts +49 -0
- package/dist/knowledge/UbiquitousLanguageManager.js +133 -0
- package/dist/knowledge/UbiquitousLanguageManager.js.map +1 -0
- package/dist/memory/MemoryBrain.d.ts +146 -0
- package/dist/memory/MemoryBrain.js +679 -0
- package/dist/memory/MemoryBrain.js.map +1 -0
- package/dist/memory/MemoryFabric.d.ts +130 -0
- package/dist/memory/MemoryFabric.js +317 -0
- package/dist/memory/MemoryFabric.js.map +1 -0
- package/dist/memory/MemoryIntelligence.d.ts +42 -0
- package/dist/memory/MemoryIntelligence.js +215 -0
- package/dist/memory/MemoryIntelligence.js.map +1 -0
- package/dist/memory/MemoryLearning.d.ts +62 -0
- package/dist/memory/MemoryLearning.js +209 -0
- package/dist/memory/MemoryLearning.js.map +1 -0
- package/dist/memory/MemoryProviders.d.ts +165 -0
- package/dist/memory/MemoryProviders.js +940 -0
- package/dist/memory/MemoryProviders.js.map +1 -0
- package/dist/memory/MemoryReview.d.ts +65 -0
- package/dist/memory/MemoryReview.js +260 -0
- package/dist/memory/MemoryReview.js.map +1 -0
- package/dist/memory/index.d.ts +6 -0
- package/dist/memory/index.js +7 -0
- package/dist/memory/index.js.map +1 -0
- package/dist/orchestration/EffectsWiring.d.ts +8 -0
- package/dist/orchestration/EffectsWiring.js +87 -0
- package/dist/orchestration/EffectsWiring.js.map +1 -0
- package/dist/orchestrator/OrchestratorDaemon.d.ts +44 -0
- package/dist/orchestrator/OrchestratorDaemon.js +150 -0
- package/dist/orchestrator/OrchestratorDaemon.js.map +1 -0
- package/dist/orchestrator/PolicyLoader.d.ts +80 -0
- package/dist/orchestrator/PolicyLoader.js +229 -0
- package/dist/orchestrator/PolicyLoader.js.map +1 -0
- package/dist/orchestrator/ReconciliationLoop.d.ts +71 -0
- package/dist/orchestrator/ReconciliationLoop.js +266 -0
- package/dist/orchestrator/ReconciliationLoop.js.map +1 -0
- package/dist/orchestrator/TrackerAdapter.d.ts +60 -0
- package/dist/orchestrator/TrackerAdapter.js +147 -0
- package/dist/orchestrator/TrackerAdapter.js.map +1 -0
- package/dist/orchestrator/WorkspaceManager.d.ts +66 -0
- package/dist/orchestrator/WorkspaceManager.js +257 -0
- package/dist/orchestrator/WorkspaceManager.js.map +1 -0
- package/dist/output/BrandThemeLoader.d.ts +54 -0
- package/dist/output/BrandThemeLoader.js +340 -0
- package/dist/output/BrandThemeLoader.js.map +1 -0
- package/dist/output/GovernanceDashboard.d.ts +59 -0
- package/dist/output/GovernanceDashboard.js +281 -0
- package/dist/output/GovernanceDashboard.js.map +1 -0
- package/dist/output/HTMLArtifactLayer.d.ts +97 -0
- package/dist/output/HTMLArtifactLayer.js +576 -0
- package/dist/output/HTMLArtifactLayer.js.map +1 -0
- package/dist/output/HTMLDocumentRenderer.d.ts +83 -0
- package/dist/output/HTMLDocumentRenderer.js +718 -0
- package/dist/output/HTMLDocumentRenderer.js.map +1 -0
- package/dist/output/UIPrototypeRenderer.d.ts +61 -0
- package/dist/output/UIPrototypeRenderer.js +500 -0
- package/dist/output/UIPrototypeRenderer.js.map +1 -0
- package/dist/output/index.d.ts +10 -0
- package/dist/output/index.js +8 -0
- package/dist/output/index.js.map +1 -0
- package/dist/prompts/PhasePromptRegistry.d.ts +53 -0
- package/dist/prompts/PhasePromptRegistry.js +517 -0
- package/dist/prompts/PhasePromptRegistry.js.map +1 -0
- package/dist/prompts/PromptOptimizer.d.ts +42 -0
- package/dist/prompts/PromptOptimizer.js +309 -0
- package/dist/prompts/PromptOptimizer.js.map +1 -0
- package/dist/prompts/VibeTemplateGallery.d.ts +25 -0
- package/dist/prompts/VibeTemplateGallery.js +295 -0
- package/dist/prompts/VibeTemplateGallery.js.map +1 -0
- package/dist/qa/BrowserDaemon.d.ts +23 -0
- package/dist/qa/BrowserDaemon.js +79 -0
- package/dist/qa/BrowserDaemon.js.map +1 -0
- package/dist/qa/E2ETestOrchestrator.d.ts +14 -0
- package/dist/qa/E2ETestOrchestrator.js +19 -0
- package/dist/qa/E2ETestOrchestrator.js.map +1 -0
- package/dist/review/CrossModelReviewer.d.ts +35 -0
- package/dist/review/CrossModelReviewer.js +75 -0
- package/dist/review/CrossModelReviewer.js.map +1 -0
- package/dist/review/ReviewAggregator.d.ts +13 -0
- package/dist/review/ReviewAggregator.js +28 -0
- package/dist/review/ReviewAggregator.js.map +1 -0
- package/dist/review/reviewCommands.d.ts +15 -0
- package/dist/review/reviewCommands.js +24 -0
- package/dist/review/reviewCommands.js.map +1 -0
- package/dist/routing/LocalModelProvider.d.ts +11 -0
- package/dist/routing/LocalModelProvider.js +21 -0
- package/dist/routing/LocalModelProvider.js.map +1 -0
- package/dist/routing/ModelRouter.d.ts +42 -0
- package/dist/routing/ModelRouter.js +94 -0
- package/dist/routing/ModelRouter.js.map +1 -0
- package/dist/routing/PromptCachePolicy.d.ts +37 -0
- package/dist/routing/PromptCachePolicy.js +97 -0
- package/dist/routing/PromptCachePolicy.js.map +1 -0
- package/dist/runtime/AiOsRuntime.d.ts +485 -0
- package/dist/runtime/AiOsRuntime.js +1846 -0
- package/dist/runtime/AiOsRuntime.js.map +1 -0
- package/dist/runtime/CostAnalyzer.d.ts +53 -0
- package/dist/runtime/CostAnalyzer.js +160 -0
- package/dist/runtime/CostAnalyzer.js.map +1 -0
- package/dist/runtime/CostOptimizer.d.ts +11 -0
- package/dist/runtime/CostOptimizer.js +21 -0
- package/dist/runtime/CostOptimizer.js.map +1 -0
- package/dist/runtime/ExecutionLedger.d.ts +46 -0
- package/dist/runtime/ExecutionLedger.js +71 -0
- package/dist/runtime/ExecutionLedger.js.map +1 -0
- package/dist/runtime/FinalReportGuard.d.ts +16 -0
- package/dist/runtime/FinalReportGuard.js +14 -0
- package/dist/runtime/FinalReportGuard.js.map +1 -0
- package/dist/runtime/ModelUsageLedger.d.ts +101 -0
- package/dist/runtime/ModelUsageLedger.js +296 -0
- package/dist/runtime/ModelUsageLedger.js.map +1 -0
- package/dist/runtime/RuntimeDoctor.d.ts +23 -0
- package/dist/runtime/RuntimeDoctor.js +151 -0
- package/dist/runtime/RuntimeDoctor.js.map +1 -0
- package/dist/runtime/RuntimeEvidenceLedger.d.ts +50 -0
- package/dist/runtime/RuntimeEvidenceLedger.js +89 -0
- package/dist/runtime/RuntimeEvidenceLedger.js.map +1 -0
- package/dist/runtime/SessionLedger.d.ts +53 -0
- package/dist/runtime/SessionLedger.js +104 -0
- package/dist/runtime/SessionLedger.js.map +1 -0
- package/dist/runtime/index.d.ts +7 -0
- package/dist/runtime/index.js +8 -0
- package/dist/runtime/index.js.map +1 -0
- package/dist/setup/SetupVerification.d.ts +42 -0
- package/dist/setup/SetupVerification.js +180 -0
- package/dist/setup/SetupVerification.js.map +1 -0
- package/dist/setup/SetupWizard.d.ts +45 -0
- package/dist/setup/SetupWizard.js +216 -0
- package/dist/setup/SetupWizard.js.map +1 -0
- package/dist/shield/PolicyCompiler.d.ts +70 -0
- package/dist/shield/PolicyCompiler.js +540 -0
- package/dist/shield/PolicyCompiler.js.map +1 -0
- package/dist/shield/ProtectedPaths.d.ts +39 -0
- package/dist/shield/ProtectedPaths.js +179 -0
- package/dist/shield/ProtectedPaths.js.map +1 -0
- package/dist/shield/ShieldProtocol.d.ts +50 -0
- package/dist/shield/ShieldProtocol.js +103 -0
- package/dist/shield/ShieldProtocol.js.map +1 -0
- package/dist/skills/ExternalSkills.d.ts +3 -0
- package/dist/skills/ExternalSkills.js +27 -0
- package/dist/skills/ExternalSkills.js.map +1 -0
- package/dist/skills/GrillingSessionSkill.d.ts +65 -0
- package/dist/skills/GrillingSessionSkill.js +113 -0
- package/dist/skills/GrillingSessionSkill.js.map +1 -0
- package/dist/skills/GrillingTemplates.d.ts +7 -0
- package/dist/skills/GrillingTemplates.js +38 -0
- package/dist/skills/GrillingTemplates.js.map +1 -0
- package/dist/skills/RoleSkills.d.ts +20 -0
- package/dist/skills/RoleSkills.js +154 -0
- package/dist/skills/RoleSkills.js.map +1 -0
- package/dist/skills/SkillCatalog.d.ts +13 -0
- package/dist/skills/SkillCatalog.js +184 -0
- package/dist/skills/SkillCatalog.js.map +1 -0
- package/dist/skills/SkillDiscovery.d.ts +84 -0
- package/dist/skills/SkillDiscovery.js +402 -0
- package/dist/skills/SkillDiscovery.js.map +1 -0
- package/dist/skills/SkillDoctor.d.ts +37 -0
- package/dist/skills/SkillDoctor.js +267 -0
- package/dist/skills/SkillDoctor.js.map +1 -0
- package/dist/skills/SkillExecutor.d.ts +38 -0
- package/dist/skills/SkillExecutor.js +237 -0
- package/dist/skills/SkillExecutor.js.map +1 -0
- package/dist/skills/SkillFrontmatter.d.ts +28 -0
- package/dist/skills/SkillFrontmatter.js +152 -0
- package/dist/skills/SkillFrontmatter.js.map +1 -0
- package/dist/skills/SkillInstaller.d.ts +40 -0
- package/dist/skills/SkillInstaller.js +117 -0
- package/dist/skills/SkillInstaller.js.map +1 -0
- package/dist/skills/SkillMdStandard.d.ts +33 -0
- package/dist/skills/SkillMdStandard.js +88 -0
- package/dist/skills/SkillMdStandard.js.map +1 -0
- package/dist/skills/SkillRadar.d.ts +83 -0
- package/dist/skills/SkillRadar.js +404 -0
- package/dist/skills/SkillRadar.js.map +1 -0
- package/dist/skills/SkillRegistry.d.ts +112 -0
- package/dist/skills/SkillRegistry.js +161 -0
- package/dist/skills/SkillRegistry.js.map +1 -0
- package/dist/skills/SkillRepository.d.ts +71 -0
- package/dist/skills/SkillRepository.js +435 -0
- package/dist/skills/SkillRepository.js.map +1 -0
- package/dist/skills/TriggerEngine.d.ts +43 -0
- package/dist/skills/TriggerEngine.js +142 -0
- package/dist/skills/TriggerEngine.js.map +1 -0
- package/dist/skills/coreSkills.d.ts +6 -0
- package/dist/skills/coreSkills.js +41 -0
- package/dist/skills/coreSkills.js.map +1 -0
- package/dist/skills/index.d.ts +10 -0
- package/dist/skills/index.js +12 -0
- package/dist/skills/index.js.map +1 -0
- package/dist/skills/interop/GStackInterop.d.ts +15 -0
- package/dist/skills/interop/GStackInterop.js +34 -0
- package/dist/skills/interop/GStackInterop.js.map +1 -0
- package/dist/skills/interop/OMCInterop.d.ts +15 -0
- package/dist/skills/interop/OMCInterop.js +34 -0
- package/dist/skills/interop/OMCInterop.js.map +1 -0
- package/dist/skills/routing/SkillGate.d.ts +12 -0
- package/dist/skills/routing/SkillGate.js +117 -0
- package/dist/skills/routing/SkillGate.js.map +1 -0
- package/dist/skills/routing/SkillPlanner.d.ts +8 -0
- package/dist/skills/routing/SkillPlanner.js +179 -0
- package/dist/skills/routing/SkillPlanner.js.map +1 -0
- package/dist/skills/routing/SkillPolicy.d.ts +6 -0
- package/dist/skills/routing/SkillPolicy.js +336 -0
- package/dist/skills/routing/SkillPolicy.js.map +1 -0
- package/dist/skills/routing/SkillRoutingTypes.d.ts +89 -0
- package/dist/skills/routing/SkillRoutingTypes.js +2 -0
- package/dist/skills/routing/SkillRoutingTypes.js.map +1 -0
- package/dist/skills/routing/TaskIntentClassifier.d.ts +6 -0
- package/dist/skills/routing/TaskIntentClassifier.js +79 -0
- package/dist/skills/routing/TaskIntentClassifier.js.map +1 -0
- package/dist/skills/routing/index.d.ts +5 -0
- package/dist/skills/routing/index.js +6 -0
- package/dist/skills/routing/index.js.map +1 -0
- package/dist/tasks/IssueTriageFSM.d.ts +26 -0
- package/dist/tasks/IssueTriageFSM.js +107 -0
- package/dist/tasks/IssueTriageFSM.js.map +1 -0
- package/dist/tasks/TaskEngine.d.ts +97 -0
- package/dist/tasks/TaskEngine.js +289 -0
- package/dist/tasks/TaskEngine.js.map +1 -0
- package/dist/testing/DiffTestSelector.d.ts +22 -0
- package/dist/testing/DiffTestSelector.js +114 -0
- package/dist/testing/DiffTestSelector.js.map +1 -0
- package/dist/testing/index.d.ts +1 -0
- package/dist/testing/index.js +3 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/tools/CommandOutputCompressor.d.ts +28 -0
- package/dist/tools/CommandOutputCompressor.js +242 -0
- package/dist/tools/CommandOutputCompressor.js.map +1 -0
- package/dist/tools/CommandRunLedger.d.ts +77 -0
- package/dist/tools/CommandRunLedger.js +111 -0
- package/dist/tools/CommandRunLedger.js.map +1 -0
- package/dist/tools/RtkRuntime.d.ts +9 -0
- package/dist/tools/RtkRuntime.js +43 -0
- package/dist/tools/RtkRuntime.js.map +1 -0
- package/dist/tools/SafeCommandRunner.d.ts +16 -0
- package/dist/tools/SafeCommandRunner.js +83 -0
- package/dist/tools/SafeCommandRunner.js.map +1 -0
- package/dist/tools/ToolCapabilityRegistry.d.ts +51 -0
- package/dist/tools/ToolCapabilityRegistry.js +295 -0
- package/dist/tools/ToolCapabilityRegistry.js.map +1 -0
- package/dist/tools/ToolEvidenceGate.d.ts +39 -0
- package/dist/tools/ToolEvidenceGate.js +117 -0
- package/dist/tools/ToolEvidenceGate.js.map +1 -0
- package/dist/tools/ToolEvidenceStore.d.ts +58 -0
- package/dist/tools/ToolEvidenceStore.js +129 -0
- package/dist/tools/ToolEvidenceStore.js.map +1 -0
- package/dist/tools/ToolOrchestrator.d.ts +67 -0
- package/dist/tools/ToolOrchestrator.js +252 -0
- package/dist/tools/ToolOrchestrator.js.map +1 -0
- package/dist/tools/ToolPolicy.d.ts +33 -0
- package/dist/tools/ToolPolicy.js +172 -0
- package/dist/tools/ToolPolicy.js.map +1 -0
- package/dist/tools/index.d.ts +7 -0
- package/dist/tools/index.js +8 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tui/TuiDashboard.d.ts +3 -0
- package/dist/tui/TuiDashboard.js +120 -0
- package/dist/tui/TuiDashboard.js.map +1 -0
- package/dist/version.d.ts +3 -0
- package/dist/version.js +15 -0
- package/dist/version.js.map +1 -0
- package/dist/workflow/AdaptiveWorkflowRouter.d.ts +38 -0
- package/dist/workflow/AdaptiveWorkflowRouter.js +214 -0
- package/dist/workflow/AdaptiveWorkflowRouter.js.map +1 -0
- package/dist/workflow/CommitDiscipline.d.ts +68 -0
- package/dist/workflow/CommitDiscipline.js +328 -0
- package/dist/workflow/CommitDiscipline.js.map +1 -0
- package/dist/workflow/ContextGovernance.d.ts +51 -0
- package/dist/workflow/ContextGovernance.js +233 -0
- package/dist/workflow/ContextGovernance.js.map +1 -0
- package/dist/workflow/CrossRepoOrchestrator.d.ts +92 -0
- package/dist/workflow/CrossRepoOrchestrator.js +408 -0
- package/dist/workflow/CrossRepoOrchestrator.js.map +1 -0
- package/dist/workflow/DiagnosticLoop.d.ts +40 -0
- package/dist/workflow/DiagnosticLoop.js +105 -0
- package/dist/workflow/DiagnosticLoop.js.map +1 -0
- package/dist/workflow/EngineeringStandards.d.ts +212 -0
- package/dist/workflow/EngineeringStandards.js +1113 -0
- package/dist/workflow/EngineeringStandards.js.map +1 -0
- package/dist/workflow/EvidenceStore.d.ts +20 -0
- package/dist/workflow/EvidenceStore.js +48 -0
- package/dist/workflow/EvidenceStore.js.map +1 -0
- package/dist/workflow/EvolutionShadowPromoter.d.ts +46 -0
- package/dist/workflow/EvolutionShadowPromoter.js +73 -0
- package/dist/workflow/EvolutionShadowPromoter.js.map +1 -0
- package/dist/workflow/GateCatalog.d.ts +63 -0
- package/dist/workflow/GateCatalog.js +268 -0
- package/dist/workflow/GateCatalog.js.map +1 -0
- package/dist/workflow/GovernanceLock.d.ts +35 -0
- package/dist/workflow/GovernanceLock.js +58 -0
- package/dist/workflow/GovernanceLock.js.map +1 -0
- package/dist/workflow/GovernanceRoi.d.ts +52 -0
- package/dist/workflow/GovernanceRoi.js +204 -0
- package/dist/workflow/GovernanceRoi.js.map +1 -0
- package/dist/workflow/GovernanceTemplatePacks.d.ts +24 -0
- package/dist/workflow/GovernanceTemplatePacks.js +2134 -0
- package/dist/workflow/GovernanceTemplatePacks.js.map +1 -0
- package/dist/workflow/GovernanceTemplates.d.ts +19 -0
- package/dist/workflow/GovernanceTemplates.js +1291 -0
- package/dist/workflow/GovernanceTemplates.js.map +1 -0
- package/dist/workflow/McpGovernance.d.ts +63 -0
- package/dist/workflow/McpGovernance.js +198 -0
- package/dist/workflow/McpGovernance.js.map +1 -0
- package/dist/workflow/OutOfScopeStore.d.ts +37 -0
- package/dist/workflow/OutOfScopeStore.js +164 -0
- package/dist/workflow/OutOfScopeStore.js.map +1 -0
- package/dist/workflow/PhaseMarkerTracker.d.ts +63 -0
- package/dist/workflow/PhaseMarkerTracker.js +291 -0
- package/dist/workflow/PhaseMarkerTracker.js.map +1 -0
- package/dist/workflow/ResourceGovernance.d.ts +120 -0
- package/dist/workflow/ResourceGovernance.js +531 -0
- package/dist/workflow/ResourceGovernance.js.map +1 -0
- package/dist/workflow/ReviewAnalyzer.d.ts +80 -0
- package/dist/workflow/ReviewAnalyzer.js +438 -0
- package/dist/workflow/ReviewAnalyzer.js.map +1 -0
- package/dist/workflow/ReviewStore.d.ts +36 -0
- package/dist/workflow/ReviewStore.js +42 -0
- package/dist/workflow/ReviewStore.js.map +1 -0
- package/dist/workflow/SecurityAudit.d.ts +27 -0
- package/dist/workflow/SecurityAudit.js +294 -0
- package/dist/workflow/SecurityAudit.js.map +1 -0
- package/dist/workflow/SessionCoordinator.d.ts +103 -0
- package/dist/workflow/SessionCoordinator.js +401 -0
- package/dist/workflow/SessionCoordinator.js.map +1 -0
- package/dist/workflow/SessionPreamble.d.ts +19 -0
- package/dist/workflow/SessionPreamble.js +130 -0
- package/dist/workflow/SessionPreamble.js.map +1 -0
- package/dist/workflow/SessionStateTracker.d.ts +74 -0
- package/dist/workflow/SessionStateTracker.js +270 -0
- package/dist/workflow/SessionStateTracker.js.map +1 -0
- package/dist/workflow/ShipPipeline.d.ts +30 -0
- package/dist/workflow/ShipPipeline.js +366 -0
- package/dist/workflow/ShipPipeline.js.map +1 -0
- package/dist/workflow/TaskArtifactScaffolder.d.ts +69 -0
- package/dist/workflow/TaskArtifactScaffolder.js +333 -0
- package/dist/workflow/TaskArtifactScaffolder.js.map +1 -0
- package/dist/workflow/TaskDependencyGraph.d.ts +73 -0
- package/dist/workflow/TaskDependencyGraph.js +245 -0
- package/dist/workflow/TaskDependencyGraph.js.map +1 -0
- package/dist/workflow/TaskLevelDetector.d.ts +41 -0
- package/dist/workflow/TaskLevelDetector.js +219 -0
- package/dist/workflow/TaskLevelDetector.js.map +1 -0
- package/dist/workflow/TaskMetricsStore.d.ts +49 -0
- package/dist/workflow/TaskMetricsStore.js +149 -0
- package/dist/workflow/TaskMetricsStore.js.map +1 -0
- package/dist/workflow/TaskScoreEngine.d.ts +42 -0
- package/dist/workflow/TaskScoreEngine.js +181 -0
- package/dist/workflow/TaskScoreEngine.js.map +1 -0
- package/dist/workflow/TddLoop.d.ts +49 -0
- package/dist/workflow/TddLoop.js +78 -0
- package/dist/workflow/TddLoop.js.map +1 -0
- package/dist/workflow/UpgradeManager.d.ts +178 -0
- package/dist/workflow/UpgradeManager.js +665 -0
- package/dist/workflow/UpgradeManager.js.map +1 -0
- package/dist/workflow/VerificationCommands.d.ts +36 -0
- package/dist/workflow/VerificationCommands.js +123 -0
- package/dist/workflow/VerificationCommands.js.map +1 -0
- package/dist/workflow/VerificationProfile.d.ts +67 -0
- package/dist/workflow/VerificationProfile.js +241 -0
- package/dist/workflow/VerificationProfile.js.map +1 -0
- package/dist/workflow/VerificationSchema.d.ts +46 -0
- package/dist/workflow/VerificationSchema.js +97 -0
- package/dist/workflow/VerificationSchema.js.map +1 -0
- package/dist/workflow/WorkflowArtifactWriter.d.ts +113 -0
- package/dist/workflow/WorkflowArtifactWriter.js +242 -0
- package/dist/workflow/WorkflowArtifactWriter.js.map +1 -0
- package/dist/workflow/WorkflowEngine.d.ts +83 -0
- package/dist/workflow/WorkflowEngine.js +183 -0
- package/dist/workflow/WorkflowEngine.js.map +1 -0
- package/dist/workflow/WorkflowGuidance.d.ts +30 -0
- package/dist/workflow/WorkflowGuidance.js +204 -0
- package/dist/workflow/WorkflowGuidance.js.map +1 -0
- package/dist/workflow/WorkflowOpenTasks.d.ts +16 -0
- package/dist/workflow/WorkflowOpenTasks.js +37 -0
- package/dist/workflow/WorkflowOpenTasks.js.map +1 -0
- package/dist/workflow/WorkflowOrchestrator.d.ts +59 -0
- package/dist/workflow/WorkflowOrchestrator.js +326 -0
- package/dist/workflow/WorkflowOrchestrator.js.map +1 -0
- package/dist/workflow/WorkflowTemplates.d.ts +38 -0
- package/dist/workflow/WorkflowTemplates.js +371 -0
- package/dist/workflow/WorkflowTemplates.js.map +1 -0
- package/dist/workflow/WorkspaceLifecycle.d.ts +71 -0
- package/dist/workflow/WorkspaceLifecycle.js +401 -0
- package/dist/workflow/WorkspaceLifecycle.js.map +1 -0
- package/dist/workflow/WorkspacePolicy.d.ts +46 -0
- package/dist/workflow/WorkspacePolicy.js +141 -0
- package/dist/workflow/WorkspacePolicy.js.map +1 -0
- package/dist/workflow/WorkspaceSafety.d.ts +9 -0
- package/dist/workflow/WorkspaceSafety.js +49 -0
- package/dist/workflow/WorkspaceSafety.js.map +1 -0
- package/dist/workflow/WorkspaceTopology.d.ts +58 -0
- package/dist/workflow/WorkspaceTopology.js +176 -0
- package/dist/workflow/WorkspaceTopology.js.map +1 -0
- package/dist/workflow/autofix/AutoFixEngine.d.ts +37 -0
- package/dist/workflow/autofix/AutoFixEngine.js +169 -0
- package/dist/workflow/autofix/AutoFixEngine.js.map +1 -0
- package/dist/workflow/autonomous/AutonomousDevLoop.d.ts +88 -0
- package/dist/workflow/autonomous/AutonomousDevLoop.js +381 -0
- package/dist/workflow/autonomous/AutonomousDevLoop.js.map +1 -0
- package/dist/workflow/autonomous/BackgroundHunter.d.ts +74 -0
- package/dist/workflow/autonomous/BackgroundHunter.js +220 -0
- package/dist/workflow/autonomous/BackgroundHunter.js.map +1 -0
- package/dist/workflow/autonomous/WorklogManager.d.ts +50 -0
- package/dist/workflow/autonomous/WorklogManager.js +264 -0
- package/dist/workflow/autonomous/WorklogManager.js.map +1 -0
- package/dist/workflow/autonomous/index.d.ts +3 -0
- package/dist/workflow/autonomous/index.js +5 -0
- package/dist/workflow/autonomous/index.js.map +1 -0
- package/dist/workflow/cognitive/AmbiguityScorer.d.ts +17 -0
- package/dist/workflow/cognitive/AmbiguityScorer.js +107 -0
- package/dist/workflow/cognitive/AmbiguityScorer.js.map +1 -0
- package/dist/workflow/cognitive/ConsensusPlanner.d.ts +26 -0
- package/dist/workflow/cognitive/ConsensusPlanner.js +141 -0
- package/dist/workflow/cognitive/ConsensusPlanner.js.map +1 -0
- package/dist/workflow/cognitive/SocraticQuestioner.d.ts +33 -0
- package/dist/workflow/cognitive/SocraticQuestioner.js +276 -0
- package/dist/workflow/cognitive/SocraticQuestioner.js.map +1 -0
- package/dist/workflow/evolution/LessonExtractor.d.ts +90 -0
- package/dist/workflow/evolution/LessonExtractor.js +317 -0
- package/dist/workflow/evolution/LessonExtractor.js.map +1 -0
- package/dist/workflow/evolution/SelfImproveEngine.d.ts +156 -0
- package/dist/workflow/evolution/SelfImproveEngine.js +361 -0
- package/dist/workflow/evolution/SelfImproveEngine.js.map +1 -0
- package/dist/workflow/execution/RalphEngine.d.ts +54 -0
- package/dist/workflow/execution/RalphEngine.js +145 -0
- package/dist/workflow/execution/RalphEngine.js.map +1 -0
- package/dist/workflow/execution/UltraworkEngine.d.ts +43 -0
- package/dist/workflow/execution/UltraworkEngine.js +135 -0
- package/dist/workflow/execution/UltraworkEngine.js.map +1 -0
- package/dist/workflow/gates/EnhancedGates.d.ts +74 -0
- package/dist/workflow/gates/EnhancedGates.js +653 -0
- package/dist/workflow/gates/EnhancedGates.js.map +1 -0
- package/dist/workflow/gates/GateSystem.d.ts +180 -0
- package/dist/workflow/gates/GateSystem.js +1279 -0
- package/dist/workflow/gates/GateSystem.js.map +1 -0
- package/dist/workflow/gates/MetaGovernanceGates.d.ts +70 -0
- package/dist/workflow/gates/MetaGovernanceGates.js +617 -0
- package/dist/workflow/gates/MetaGovernanceGates.js.map +1 -0
- package/dist/workflow/gates/VisualGate.d.ts +41 -0
- package/dist/workflow/gates/VisualGate.js +174 -0
- package/dist/workflow/gates/VisualGate.js.map +1 -0
- package/dist/workflow/index.d.ts +45 -0
- package/dist/workflow/index.js +47 -0
- package/dist/workflow/index.js.map +1 -0
- package/dist/workflow/qa/E2ETestRunner.d.ts +102 -0
- package/dist/workflow/qa/E2ETestRunner.js +227 -0
- package/dist/workflow/qa/E2ETestRunner.js.map +1 -0
- package/dist/workflow/quality/HonestDelivery.d.ts +19 -0
- package/dist/workflow/quality/HonestDelivery.js +77 -0
- package/dist/workflow/quality/HonestDelivery.js.map +1 -0
- package/dist/workflow/quality/KarpathyEvaluator.d.ts +18 -0
- package/dist/workflow/quality/KarpathyEvaluator.js +76 -0
- package/dist/workflow/quality/KarpathyEvaluator.js.map +1 -0
- package/dist/workflow/types.d.ts +151 -0
- package/dist/workflow/types.js +4 -0
- package/dist/workflow/types.js.map +1 -0
- package/dist/workflows/DAGBuilder.d.ts +52 -0
- package/dist/workflows/DAGBuilder.js +169 -0
- package/dist/workflows/DAGBuilder.js.map +1 -0
- package/dist/workflows/GateParser.d.ts +55 -0
- package/dist/workflows/GateParser.js +73 -0
- package/dist/workflows/GateParser.js.map +1 -0
- package/dist/workflows/WorkflowExecutor.d.ts +56 -0
- package/dist/workflows/WorkflowExecutor.js +143 -0
- package/dist/workflows/WorkflowExecutor.js.map +1 -0
- package/dist/workflows/WorkflowOrchestrator.d.ts +81 -0
- package/dist/workflows/WorkflowOrchestrator.js +337 -0
- package/dist/workflows/WorkflowOrchestrator.js.map +1 -0
- package/dist/workflows/index.d.ts +2 -0
- package/dist/workflows/index.js +5 -0
- package/dist/workflows/index.js.map +1 -0
- package/dist/workflows/presets.d.ts +34 -0
- package/dist/workflows/presets.js +224 -0
- package/dist/workflows/presets.js.map +1 -0
- package/docs/README.md +105 -0
- package/docs/guides/DEVELOPMENT_WORKFLOW.md +99 -0
- package/docs/guides/GETTING_STARTED.md +93 -0
- package/docs/guides/MEDICAL_AGENT_OPERATING_GUIDE.md +61 -0
- package/docs/guides/MEDICAL_RESEARCH_DELIVERY.md +217 -0
- package/docs/guides/MIGRATION.md +119 -0
- package/docs/reference/cli.md +2921 -0
- package/docs/start/README.md +79 -0
- package/docs/start/agent-governance-demo.md +107 -0
- package/docs/start/artifact-lifecycle.md +326 -0
- package/docs/start/quickstart.md +191 -0
- package/docs/start/workflow-upgrade.md +198 -0
- package/docs/workflow/GATES_AND_SCORE.md +89 -0
- package/docs/workflow/PROMPT_OPTIMIZATION.md +44 -0
- package/docs/workflow/README.md +123 -0
- package/docs/workflow/node-library.md +52 -0
- package/docs/workflow/templates/api-contract.md +29 -0
- package/docs/workflow/templates/architecture-review.md +23 -0
- package/docs/workflow/templates/db-change-plan.md +20 -0
- package/docs/workflow/templates/docs-impact.md +17 -0
- package/docs/workflow/templates/e2e-plan.md +20 -0
- package/docs/workflow/templates/explore.md +16 -0
- package/docs/workflow/templates/github-actions-scale-preflight.yml +32 -0
- package/docs/workflow/templates/mini-prd.md +16 -0
- package/docs/workflow/templates/plan.md +37 -0
- package/docs/workflow/templates/pre-push-scale-preflight.sh +8 -0
- package/docs/workflow/templates/product-smoke.md +61 -0
- package/docs/workflow/templates/reality-check.md +28 -0
- package/docs/workflow/templates/resource-cleanup.md +17 -0
- package/docs/workflow/templates/resource-impact.md +25 -0
- package/docs/workflow/templates/review.md +12 -0
- package/docs/workflow/templates/runtime.md +23 -0
- package/docs/workflow/templates/security-review.md +26 -0
- package/docs/workflow/templates/skill-evidence.md +33 -0
- package/docs/workflow/templates/skill-plan.md +39 -0
- package/docs/workflow/templates/spec.md +17 -0
- package/docs/workflow/templates/standards-impact.md +28 -0
- package/docs/workflow/templates/summary.md +16 -0
- package/docs/workflow/templates/tasks.md +8 -0
- package/docs/workflow/templates/ui-spec.md +29 -0
- package/docs/workflow/templates/verification.md +20 -0
- package/docs/workflow/templates/visual-review.md +20 -0
- package/docs/zh/quickstart.md +108 -0
- package/examples/demo-projects/agent-governance-demo/CONTEXT.md +14 -0
- package/examples/demo-projects/agent-governance-demo/README.md +48 -0
- package/examples/demo-projects/agent-governance-demo/docs/CONTEXT-MAP.md +14 -0
- package/examples/demo-projects/agent-governance-demo/package.json +22 -0
- package/examples/demo-projects/agent-governance-demo/src/oauth-state.ts +39 -0
- package/examples/demo-projects/agent-governance-demo/tests/oauth-state.test.ts +52 -0
- package/mcp-configs/_INDEX.md +55 -0
- package/mcp-configs/context7/config.json +9 -0
- package/mcp-configs/fetch/config.json +9 -0
- package/mcp-configs/filesystem/config.json +9 -0
- package/mcp-configs/github/config.json +11 -0
- package/mcp-configs/memory/config.json +9 -0
- package/mcp-configs/neon/config.json +11 -0
- package/mcp-configs/playwright/config.json +9 -0
- package/mcp-configs/postgres/config.json +11 -0
- package/mcp-configs/puppeteer/config.json +9 -0
- package/mcp-configs/sequential-thinking/config.json +9 -0
- package/package.json +113 -0
- package/scripts/workflow/lib/gbrain-runtime.mjs +185 -0
- package/scripts/workflow/lib/report-output.mjs +107 -0
- package/scripts/workflow/medscale-release-smoke.mjs +338 -0
- package/scripts/workflow/provider-rehearsal.mjs +597 -0
- package/scripts/workflow/setup-smoke.mjs +433 -0
- package/target-research-platform/bridge_runner.py +310 -0
- package/target-research-platform/config.yaml +148 -0
- package/target-research-platform/data/immune_infiltration/ACC.csv +201 -0
- package/target-research-platform/data/immune_infiltration/BLCA.csv +201 -0
- package/target-research-platform/data/immune_infiltration/BRCA.csv +201 -0
- package/target-research-platform/data/immune_infiltration/CESC.csv +201 -0
- package/target-research-platform/data/immune_infiltration/CHOL.csv +201 -0
- package/target-research-platform/data/immune_infiltration/COAD.csv +201 -0
- package/target-research-platform/data/immune_infiltration/DLBC.csv +201 -0
- package/target-research-platform/data/immune_infiltration/ESCA.csv +201 -0
- package/target-research-platform/data/immune_infiltration/GBM.csv +201 -0
- package/target-research-platform/data/immune_infiltration/HNSC.csv +201 -0
- package/target-research-platform/data/immune_infiltration/KICH.csv +201 -0
- package/target-research-platform/data/immune_infiltration/KIRC.csv +201 -0
- package/target-research-platform/data/immune_infiltration/KIRP.csv +201 -0
- package/target-research-platform/data/immune_infiltration/LAML.csv +201 -0
- package/target-research-platform/data/immune_infiltration/LGG.csv +201 -0
- package/target-research-platform/data/immune_infiltration/LIHC.csv +201 -0
- package/target-research-platform/data/immune_infiltration/LUAD.csv +201 -0
- package/target-research-platform/data/immune_infiltration/LUSC.csv +201 -0
- package/target-research-platform/data/immune_infiltration/MESO.csv +201 -0
- package/target-research-platform/data/immune_infiltration/OV.csv +201 -0
- package/target-research-platform/data/immune_infiltration/PAAD.csv +201 -0
- package/target-research-platform/data/immune_infiltration/PCPG.csv +201 -0
- package/target-research-platform/data/immune_infiltration/PRAD.csv +201 -0
- package/target-research-platform/data/immune_infiltration/READ.csv +201 -0
- package/target-research-platform/data/immune_infiltration/SARC.csv +201 -0
- package/target-research-platform/data/immune_infiltration/SKCM.csv +201 -0
- package/target-research-platform/data/immune_infiltration/STAD.csv +201 -0
- package/target-research-platform/data/immune_infiltration/TGCT.csv +201 -0
- package/target-research-platform/data/immune_infiltration/THCA.csv +201 -0
- package/target-research-platform/data/immune_infiltration/THYM.csv +201 -0
- package/target-research-platform/data/immune_infiltration/UCEC.csv +201 -0
- package/target-research-platform/data/immune_infiltration/UCS.csv +201 -0
- package/target-research-platform/data/immune_infiltration/UVM.csv +201 -0
- package/target-research-platform/docs/JSON_SCHEMA.md +352 -0
- package/target-research-platform/docs/PROGRESS_2026-06-09.md +140 -0
- package/target-research-platform/main.py +755 -0
- package/target-research-platform/pyproject.toml +54 -0
- package/target-research-platform/requirements.txt +9 -0
- package/target-research-platform/run_dashboard.py +5 -0
- package/target-research-platform/run_real_medical_workflow.py +28 -0
- package/target-research-platform/scripts/demo_report.py +481 -0
- package/target-research-platform/scripts/generate_evidence_package.py +75 -0
- package/target-research-platform/scripts/prepare_immune_data.py +386 -0
- package/target-research-platform/src/__init__.py +4 -0
- package/target-research-platform/src/analysis/__init__.py +3 -0
- package/target-research-platform/src/analysis/stage1_expression/__init__.py +12 -0
- package/target-research-platform/src/analysis/stage1_expression/immune_correlation.py +366 -0
- package/target-research-platform/src/analysis/stage1_expression/pipeline.py +579 -0
- package/target-research-platform/src/analysis/stage1_expression/survival_analysis.py +382 -0
- package/target-research-platform/src/analysis/stage2_screening/__init__.py +12 -0
- package/target-research-platform/src/analysis/stage2_screening/differential_expression.py +451 -0
- package/target-research-platform/src/analysis/stage2_screening/druggability_score.py +477 -0
- package/target-research-platform/src/analysis/stage2_screening/pipeline.py +822 -0
- package/target-research-platform/src/analysis/stage3_deep_research/__init__.py +12 -0
- package/target-research-platform/src/analysis/stage3_deep_research/competitive_landscape.py +479 -0
- package/target-research-platform/src/analysis/stage3_deep_research/pipeline.py +763 -0
- package/target-research-platform/src/analysis/stage3_deep_research/target_function.py +290 -0
- package/target-research-platform/src/analysis/stage4_business/__init__.py +12 -0
- package/target-research-platform/src/analysis/stage4_business/business_plan.py +816 -0
- package/target-research-platform/src/analysis/stage4_business/milestone_planner.py +844 -0
- package/target-research-platform/src/analysis/stage4_business/pipeline.py +284 -0
- package/target-research-platform/src/api_clients/__init__.py +29 -0
- package/target-research-platform/src/api_clients/aminer_client.py +163 -0
- package/target-research-platform/src/api_clients/base_client.py +186 -0
- package/target-research-platform/src/api_clients/clinicaltrials_client.py +411 -0
- package/target-research-platform/src/api_clients/geo_client.py +1420 -0
- package/target-research-platform/src/api_clients/gtex_client.py +209 -0
- package/target-research-platform/src/api_clients/hpa_client.py +170 -0
- package/target-research-platform/src/api_clients/immune_data_manager.py +247 -0
- package/target-research-platform/src/api_clients/openalex_client.py +86 -0
- package/target-research-platform/src/api_clients/opentargets_client.py +558 -0
- package/target-research-platform/src/api_clients/pubmed_client.py +361 -0
- package/target-research-platform/src/api_clients/tcga_client.py +712 -0
- package/target-research-platform/src/api_clients/timer_client.py +169 -0
- package/target-research-platform/src/dashboard/app.py +2283 -0
- package/target-research-platform/src/dashboard/deliverables.py +710 -0
- package/target-research-platform/src/dashboard/static/artifactPreview.js +96 -0
- package/target-research-platform/src/dashboard/static/index.html +1292 -0
- package/target-research-platform/src/dashboard/status_tracker.py +237 -0
- package/target-research-platform/src/output/__init__.py +42 -0
- package/target-research-platform/src/output/agent_llm_provider.py +171 -0
- package/target-research-platform/src/output/chart_generator.py +550 -0
- package/target-research-platform/src/output/data_exporter.py +215 -0
- package/target-research-platform/src/output/delivery_packager.py +791 -0
- package/target-research-platform/src/output/evidence_package.py +230 -0
- package/target-research-platform/src/output/journal_templates.py +193 -0
- package/target-research-platform/src/output/literature_enricher.py +395 -0
- package/target-research-platform/src/output/literature_reviewer.py +420 -0
- package/target-research-platform/src/output/manuscript_fact_checker.py +279 -0
- package/target-research-platform/src/output/manuscript_generator.py +1189 -0
- package/target-research-platform/src/output/manuscript_quality.py +401 -0
- package/target-research-platform/src/output/medical_skills_bridge.py +140 -0
- package/target-research-platform/src/output/report_generator.py +542 -0
- package/target-research-platform/src/output/usage_envelope.py +193 -0
- package/target-research-platform/src/utils/__init__.py +1 -0
- package/target-research-platform/src/utils/config.py +125 -0
- package/target-research-platform/src/utils/logger.py +71 -0
- package/target-research-platform/src/validators/__init__.py +13 -0
- package/target-research-platform/src/validators/cross_validator.py +394 -0
- package/target-research-platform/src/validators/data_provenance.py +298 -0
|
@@ -0,0 +1,2283 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import json
|
|
5
|
+
import shutil
|
|
6
|
+
import subprocess
|
|
7
|
+
import sys
|
|
8
|
+
import threading
|
|
9
|
+
import uuid
|
|
10
|
+
from datetime import datetime
|
|
11
|
+
from functools import partial
|
|
12
|
+
from http import HTTPStatus
|
|
13
|
+
from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from urllib.parse import parse_qs, quote, urlparse
|
|
16
|
+
|
|
17
|
+
from ..output.delivery_packager import audit_delivery_package
|
|
18
|
+
from .deliverables import (
|
|
19
|
+
build_comparison_report,
|
|
20
|
+
build_report_bundle,
|
|
21
|
+
discover_target_profiles,
|
|
22
|
+
list_researcher_deliverables,
|
|
23
|
+
summarize_json_preview,
|
|
24
|
+
)
|
|
25
|
+
from .status_tracker import LiveStatusTracker, build_idle_status
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def build_content_disposition(filename: str, inline: bool = False) -> str:
|
|
29
|
+
disposition = "inline" if inline else "attachment"
|
|
30
|
+
ascii_filename = filename.encode("ascii", "ignore").decode("ascii").strip()
|
|
31
|
+
ascii_filename = ascii_filename.replace("\\", "_").replace('"', "")
|
|
32
|
+
if not ascii_filename:
|
|
33
|
+
ascii_filename = "download"
|
|
34
|
+
return (
|
|
35
|
+
f"{disposition}; filename=\"{ascii_filename}\"; "
|
|
36
|
+
f"filename*=UTF-8''{quote(filename)}"
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class DashboardState:
|
|
41
|
+
def __init__(self, base_dir: Path):
|
|
42
|
+
self.base_dir = base_dir
|
|
43
|
+
self.output_dir = base_dir / "output"
|
|
44
|
+
self.runtime_dir = self.output_dir / "dashboard_runtime"
|
|
45
|
+
self.runtime_dir.mkdir(parents=True, exist_ok=True)
|
|
46
|
+
self.status_file = self.runtime_dir / "live_status.json"
|
|
47
|
+
self.tracker = LiveStatusTracker(self.status_file)
|
|
48
|
+
self._process: subprocess.Popen[str] | None = None
|
|
49
|
+
self._lock = threading.Lock()
|
|
50
|
+
|
|
51
|
+
def load_status(self) -> dict:
|
|
52
|
+
if not self.status_file.exists():
|
|
53
|
+
return self._attach_medical_research_os(build_idle_status())
|
|
54
|
+
try:
|
|
55
|
+
status = json.loads(self.status_file.read_text(encoding="utf-8"))
|
|
56
|
+
status = self._attach_delivery_readiness(status)
|
|
57
|
+
status = self._attach_stage_completion(status)
|
|
58
|
+
status = self._attach_status_label(status)
|
|
59
|
+
status = self._attach_medical_research_os(status)
|
|
60
|
+
return status
|
|
61
|
+
except Exception as exc:
|
|
62
|
+
return self._attach_medical_research_os({
|
|
63
|
+
**build_idle_status(),
|
|
64
|
+
"status": "failed",
|
|
65
|
+
"logs": [{"timestamp": "", "level": "error", "message": f"Status file read failed: {exc}"}],
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
def _attach_delivery_readiness(self, status: dict) -> dict:
|
|
69
|
+
"""Attach researcher-facing delivery readiness fields from the manifest."""
|
|
70
|
+
if not isinstance(status, dict):
|
|
71
|
+
return status
|
|
72
|
+
artifacts = status.get("artifacts") or {}
|
|
73
|
+
manifest_path = self._resolve_artifact_path(artifacts.get("delivery_manifest"))
|
|
74
|
+
manifest = self._read_json(manifest_path) if manifest_path else {}
|
|
75
|
+
if not manifest:
|
|
76
|
+
return status
|
|
77
|
+
|
|
78
|
+
summary = status.setdefault("summary", {})
|
|
79
|
+
if not isinstance(summary, dict):
|
|
80
|
+
summary = {}
|
|
81
|
+
status["summary"] = summary
|
|
82
|
+
package_path = self._resolve_artifact_path(
|
|
83
|
+
artifacts.get("delivery_package") or summary.get("delivery_package")
|
|
84
|
+
)
|
|
85
|
+
package_audit = self._resolve_package_audit(summary.get("package_audit"), package_path, manifest)
|
|
86
|
+
if package_audit:
|
|
87
|
+
summary["package_audit"] = package_audit
|
|
88
|
+
quality_gate = self._dict_value(summary.get("quality_gate")) or self._dict_value(manifest.get("quality_gate"))
|
|
89
|
+
fact_check = self._dict_value(summary.get("fact_check")) or self._dict_value(manifest.get("fact_check"))
|
|
90
|
+
if quality_gate:
|
|
91
|
+
summary["quality_gate"] = quality_gate
|
|
92
|
+
if fact_check:
|
|
93
|
+
summary["fact_check"] = fact_check
|
|
94
|
+
memory_seed = self._memory_seed_status(manifest_path)
|
|
95
|
+
learning_candidate = self._learning_candidate_status(manifest_path)
|
|
96
|
+
summary["delivery_readiness"] = {
|
|
97
|
+
"manifest_path": str(manifest_path),
|
|
98
|
+
"quality_findings": manifest.get("quality_findings") or [],
|
|
99
|
+
"human_review_todo": manifest.get("human_review_todo") or [],
|
|
100
|
+
"literature_evidence": self._literature_evidence(manifest),
|
|
101
|
+
"review_examples": self._literature_review_examples(manifest),
|
|
102
|
+
"review_files": self._literature_review_files(manifest),
|
|
103
|
+
"package_audit": package_audit,
|
|
104
|
+
"memory_seed": memory_seed,
|
|
105
|
+
"learning_candidate": learning_candidate,
|
|
106
|
+
"researcher_cards": self._researcher_readiness_cards(
|
|
107
|
+
quality_gate,
|
|
108
|
+
fact_check,
|
|
109
|
+
manifest.get("quality_findings") or [],
|
|
110
|
+
package_audit,
|
|
111
|
+
memory_seed,
|
|
112
|
+
learning_candidate,
|
|
113
|
+
),
|
|
114
|
+
"disclaimer": manifest.get("disclaimer"),
|
|
115
|
+
}
|
|
116
|
+
return status
|
|
117
|
+
|
|
118
|
+
def _attach_stage_completion(self, status: dict) -> dict:
|
|
119
|
+
if not isinstance(status, dict):
|
|
120
|
+
return status
|
|
121
|
+
|
|
122
|
+
target = str(status.get("target") or "").strip()
|
|
123
|
+
disease = str(status.get("disease") or "").strip()
|
|
124
|
+
bundle = build_report_bundle(
|
|
125
|
+
self.output_dir,
|
|
126
|
+
current_target=target or None,
|
|
127
|
+
current_disease=disease or None,
|
|
128
|
+
)
|
|
129
|
+
sections = {
|
|
130
|
+
item.get("id"): item
|
|
131
|
+
for item in bundle.get("sections") or []
|
|
132
|
+
if isinstance(item, dict) and item.get("id")
|
|
133
|
+
}
|
|
134
|
+
profile = self._matching_profile(target, disease)
|
|
135
|
+
|
|
136
|
+
default_stages = build_idle_status()["stages"]
|
|
137
|
+
stages = status.get("stages")
|
|
138
|
+
if not isinstance(stages, dict):
|
|
139
|
+
stages = default_stages
|
|
140
|
+
status["stages"] = stages
|
|
141
|
+
|
|
142
|
+
for stage_key in ("stage1", "stage2", "stage3", "stage4"):
|
|
143
|
+
stage_state = stages.setdefault(stage_key, default_stages[stage_key].copy())
|
|
144
|
+
section = sections.get(stage_key) or {}
|
|
145
|
+
artifact_path = section.get("path")
|
|
146
|
+
if artifact_path and stage_state.get("status") != "failed":
|
|
147
|
+
stage_state["status"] = "completed"
|
|
148
|
+
stage_state["finished_at"] = stage_state.get("finished_at") or self._mtime_iso(artifact_path)
|
|
149
|
+
metrics = ((profile.get("metrics") or {}).get(stage_key) or {}) if profile else {}
|
|
150
|
+
if metrics:
|
|
151
|
+
merged_metrics = dict(stage_state.get("metrics") or {})
|
|
152
|
+
for key, value in metrics.items():
|
|
153
|
+
if value not in (None, "", [], {}):
|
|
154
|
+
merged_metrics[key] = value
|
|
155
|
+
stage_state["metrics"] = merged_metrics
|
|
156
|
+
|
|
157
|
+
return status
|
|
158
|
+
|
|
159
|
+
def _attach_status_label(self, status: dict) -> dict:
|
|
160
|
+
if not isinstance(status, dict):
|
|
161
|
+
return status
|
|
162
|
+
if status.get("status") == "completed" and not status.get("current_stage_label"):
|
|
163
|
+
artifacts = status.get("artifacts") or {}
|
|
164
|
+
if artifacts.get("delivery_package"):
|
|
165
|
+
status["current_stage_label"] = "交付包已就绪"
|
|
166
|
+
else:
|
|
167
|
+
completed = sum(
|
|
168
|
+
1
|
|
169
|
+
for stage in (status.get("stages") or {}).values()
|
|
170
|
+
if isinstance(stage, dict) and stage.get("status") == "completed"
|
|
171
|
+
)
|
|
172
|
+
if completed == 4:
|
|
173
|
+
status["current_stage_label"] = "研究流程已完成"
|
|
174
|
+
return status
|
|
175
|
+
|
|
176
|
+
def _matching_profile(self, target: str, disease: str) -> dict | None:
|
|
177
|
+
if not target:
|
|
178
|
+
return None
|
|
179
|
+
for profile in discover_target_profiles(self.output_dir):
|
|
180
|
+
if str(profile.get("target") or "").strip().lower() != target.lower():
|
|
181
|
+
continue
|
|
182
|
+
if disease and str(profile.get("disease") or "").strip().lower() != disease.lower():
|
|
183
|
+
continue
|
|
184
|
+
return profile
|
|
185
|
+
return None
|
|
186
|
+
|
|
187
|
+
def _resolve_package_audit(
|
|
188
|
+
self,
|
|
189
|
+
inline_value: object,
|
|
190
|
+
package_path: Path | None,
|
|
191
|
+
manifest: dict[str, object],
|
|
192
|
+
) -> dict:
|
|
193
|
+
if isinstance(inline_value, dict) and inline_value:
|
|
194
|
+
return inline_value
|
|
195
|
+
if not package_path or not manifest:
|
|
196
|
+
return {}
|
|
197
|
+
return audit_delivery_package(package_path, manifest)
|
|
198
|
+
|
|
199
|
+
@staticmethod
|
|
200
|
+
def _mtime_iso(path: str | Path) -> str | None:
|
|
201
|
+
try:
|
|
202
|
+
return datetime.fromtimestamp(Path(path).stat().st_mtime).isoformat()
|
|
203
|
+
except Exception:
|
|
204
|
+
return None
|
|
205
|
+
|
|
206
|
+
def _resolve_artifact_path(self, raw_path: str | None) -> Path | None:
|
|
207
|
+
if not raw_path:
|
|
208
|
+
return None
|
|
209
|
+
raw = Path(raw_path)
|
|
210
|
+
candidates = []
|
|
211
|
+
if raw.is_absolute():
|
|
212
|
+
candidates.append(raw)
|
|
213
|
+
else:
|
|
214
|
+
candidates.extend([
|
|
215
|
+
self.base_dir / raw,
|
|
216
|
+
self.base_dir.parent / raw,
|
|
217
|
+
self.output_dir / raw,
|
|
218
|
+
])
|
|
219
|
+
for candidate in candidates:
|
|
220
|
+
try:
|
|
221
|
+
resolved = candidate.resolve()
|
|
222
|
+
except Exception:
|
|
223
|
+
continue
|
|
224
|
+
if resolved.exists():
|
|
225
|
+
return resolved
|
|
226
|
+
return candidates[0].resolve() if candidates else None
|
|
227
|
+
|
|
228
|
+
@staticmethod
|
|
229
|
+
def _read_json(path: Path | None) -> dict:
|
|
230
|
+
if not path or not path.exists():
|
|
231
|
+
return {}
|
|
232
|
+
try:
|
|
233
|
+
payload = json.loads(path.read_text(encoding="utf-8-sig"))
|
|
234
|
+
return payload if isinstance(payload, dict) else {}
|
|
235
|
+
except Exception:
|
|
236
|
+
return {}
|
|
237
|
+
|
|
238
|
+
@staticmethod
|
|
239
|
+
def _dict_value(value: object) -> dict:
|
|
240
|
+
return value if isinstance(value, dict) else {}
|
|
241
|
+
|
|
242
|
+
@staticmethod
|
|
243
|
+
def _bool_state(value: object) -> str:
|
|
244
|
+
if value is True:
|
|
245
|
+
return "PASS"
|
|
246
|
+
if value is False:
|
|
247
|
+
return "FAIL"
|
|
248
|
+
return "UNKNOWN"
|
|
249
|
+
|
|
250
|
+
@staticmethod
|
|
251
|
+
def _int_value(value: object, default: int = 0) -> int:
|
|
252
|
+
try:
|
|
253
|
+
return int(value or 0)
|
|
254
|
+
except (TypeError, ValueError):
|
|
255
|
+
return default
|
|
256
|
+
|
|
257
|
+
@staticmethod
|
|
258
|
+
def _list_count(value: object) -> int:
|
|
259
|
+
return len(value) if isinstance(value, list) else 0
|
|
260
|
+
|
|
261
|
+
@staticmethod
|
|
262
|
+
def _card(
|
|
263
|
+
card_id: str,
|
|
264
|
+
title: str,
|
|
265
|
+
state: str,
|
|
266
|
+
status_label: str,
|
|
267
|
+
value: str,
|
|
268
|
+
summary: str,
|
|
269
|
+
details: list[str],
|
|
270
|
+
next_action: str,
|
|
271
|
+
) -> dict:
|
|
272
|
+
return {
|
|
273
|
+
"id": card_id,
|
|
274
|
+
"title": title,
|
|
275
|
+
"state": state,
|
|
276
|
+
"status_label": status_label,
|
|
277
|
+
"value": value,
|
|
278
|
+
"summary": summary,
|
|
279
|
+
"details": [item for item in details if item],
|
|
280
|
+
"next_action": next_action,
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
def _quality_researcher_card(self, quality: dict, findings: list[object]) -> dict:
|
|
284
|
+
passed = quality.get("passed") if quality else None
|
|
285
|
+
errors = self._int_value(quality.get("errors") if quality else 0)
|
|
286
|
+
warnings = self._int_value(quality.get("warnings") if quality else 0)
|
|
287
|
+
score = quality.get("score") if quality else None
|
|
288
|
+
grade = str(quality.get("grade") or "").strip() if quality else ""
|
|
289
|
+
verdict = self._bool_state(passed)
|
|
290
|
+
value_parts = [verdict]
|
|
291
|
+
if grade:
|
|
292
|
+
value_parts.append(grade)
|
|
293
|
+
if score not in (None, ""):
|
|
294
|
+
value_parts.append(str(score))
|
|
295
|
+
|
|
296
|
+
if passed is False:
|
|
297
|
+
state = "fail"
|
|
298
|
+
status_label = "需修复"
|
|
299
|
+
summary = "论文质量门禁未通过,不建议进入外部研究者审阅。"
|
|
300
|
+
next_action = "先修复质量错误和关键 findings,再重新生成交付物。"
|
|
301
|
+
elif passed is True and (warnings > 0 or findings):
|
|
302
|
+
state = "warning"
|
|
303
|
+
status_label = "通过但需复核"
|
|
304
|
+
summary = "核心质量门禁通过,但仍有警告或审阅提醒需要人工确认。"
|
|
305
|
+
next_action = "投递前逐项复核 warnings 与质量 findings。"
|
|
306
|
+
elif passed is True:
|
|
307
|
+
state = "pass"
|
|
308
|
+
status_label = "可审阅"
|
|
309
|
+
summary = "结构、边界声明、图表和元数据质量门禁已通过。"
|
|
310
|
+
next_action = "进入医学专家和作者团队复核。"
|
|
311
|
+
else:
|
|
312
|
+
state = "pending"
|
|
313
|
+
status_label = "等待报告"
|
|
314
|
+
summary = "尚未读取到论文质量门禁报告。"
|
|
315
|
+
next_action = "生成或打包论文后刷新质量状态。"
|
|
316
|
+
|
|
317
|
+
finding_details: list[str] = []
|
|
318
|
+
for item in findings[:3]:
|
|
319
|
+
if not isinstance(item, dict):
|
|
320
|
+
continue
|
|
321
|
+
finding_details.append(
|
|
322
|
+
f"{item.get('severity') or 'finding'}: {item.get('id') or 'quality'}"
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
return self._card(
|
|
326
|
+
"quality",
|
|
327
|
+
"论文质量",
|
|
328
|
+
state,
|
|
329
|
+
status_label,
|
|
330
|
+
" ".join(value_parts),
|
|
331
|
+
summary,
|
|
332
|
+
[
|
|
333
|
+
f"score={score if score not in (None, '') else '-'}; grade={grade or '-'}",
|
|
334
|
+
f"errors={errors}; warnings={warnings}; findings={len(findings)}",
|
|
335
|
+
*finding_details,
|
|
336
|
+
],
|
|
337
|
+
next_action,
|
|
338
|
+
)
|
|
339
|
+
|
|
340
|
+
def _fact_check_researcher_card(self, fact: dict) -> dict:
|
|
341
|
+
passed = fact.get("passed") if fact else None
|
|
342
|
+
errors = self._int_value(fact.get("errors") if fact else 0)
|
|
343
|
+
warnings = self._int_value(fact.get("warnings") if fact else 0)
|
|
344
|
+
score = fact.get("score") if fact else None
|
|
345
|
+
unverified = self._list_count(fact.get("unverified_claims") if fact else None)
|
|
346
|
+
failed_claims = self._list_count(fact.get("failed_claims") if fact else None)
|
|
347
|
+
verdict = self._bool_state(passed)
|
|
348
|
+
|
|
349
|
+
if passed is False:
|
|
350
|
+
state = "fail"
|
|
351
|
+
status_label = "事实阻断"
|
|
352
|
+
summary = "事实核查发现硬错误,当前稿件不应发送给研究者。"
|
|
353
|
+
next_action = "修复 PMID、数字、图表引用或医学边界错误后重跑核查。"
|
|
354
|
+
elif passed is True and (warnings > 0 or unverified > 0):
|
|
355
|
+
state = "warning"
|
|
356
|
+
status_label = "需人工确认"
|
|
357
|
+
summary = "事实核查通过,但仍有未验证或警告项需要专家确认。"
|
|
358
|
+
next_action = "人工确认未验证 claims,并补充可追溯证据。"
|
|
359
|
+
elif passed is True:
|
|
360
|
+
state = "pass"
|
|
361
|
+
status_label = "可审阅"
|
|
362
|
+
summary = "关键医学事实、PMID、数字和图表引用通过自动核查。"
|
|
363
|
+
next_action = "进入人工医学事实复核。"
|
|
364
|
+
else:
|
|
365
|
+
state = "pending"
|
|
366
|
+
status_label = "等待核查"
|
|
367
|
+
summary = "尚未读取到事实核查报告。"
|
|
368
|
+
next_action = "生成事实核查报告后再判断交付 readiness。"
|
|
369
|
+
|
|
370
|
+
return self._card(
|
|
371
|
+
"fact_check",
|
|
372
|
+
"事实核查",
|
|
373
|
+
state,
|
|
374
|
+
status_label,
|
|
375
|
+
f"{verdict} {score if score not in (None, '') else '-'}",
|
|
376
|
+
summary,
|
|
377
|
+
[
|
|
378
|
+
f"errors={errors}; warnings={warnings}",
|
|
379
|
+
f"unverified_claims={unverified}; failed_claims={failed_claims}",
|
|
380
|
+
],
|
|
381
|
+
next_action,
|
|
382
|
+
)
|
|
383
|
+
|
|
384
|
+
def _package_audit_researcher_card(self, package_audit: dict) -> dict:
|
|
385
|
+
passed = package_audit.get("passed") if package_audit else None
|
|
386
|
+
errors = self._int_value(package_audit.get("errors") if package_audit else 0)
|
|
387
|
+
warnings = self._int_value(package_audit.get("warnings") if package_audit else 0)
|
|
388
|
+
broken_images = self._list_count(package_audit.get("broken_image_refs") if package_audit else None)
|
|
389
|
+
broken_links = self._list_count(package_audit.get("broken_markdown_refs") if package_audit else None)
|
|
390
|
+
checked = self._int_value(package_audit.get("files_checked") if package_audit else 0)
|
|
391
|
+
entries = self._int_value(package_audit.get("entry_count") if package_audit else 0)
|
|
392
|
+
|
|
393
|
+
if passed is False:
|
|
394
|
+
state = "fail"
|
|
395
|
+
status_label = "ZIP 不可交付"
|
|
396
|
+
value = "FAIL"
|
|
397
|
+
summary = "交付 ZIP 已生成但归档自检失败,可能存在断链或缺失文件。"
|
|
398
|
+
next_action = "修复断链、manifest 或 README 后重新打包。"
|
|
399
|
+
elif passed is True:
|
|
400
|
+
state = "pass"
|
|
401
|
+
status_label = "ZIP 可交付"
|
|
402
|
+
value = "PASS"
|
|
403
|
+
summary = "ZIP 文件、README、manifest 和本地图表引用通过自检。"
|
|
404
|
+
next_action = "可下载交给研究者审阅,但仍需人工医学复核。"
|
|
405
|
+
elif entries or checked:
|
|
406
|
+
state = "warning"
|
|
407
|
+
status_label = "自检未定"
|
|
408
|
+
value = "REVIEW"
|
|
409
|
+
summary = "已有归档检查信号,但未得到明确通过结果。"
|
|
410
|
+
next_action = "复核 package audit 输出并重新打包确认。"
|
|
411
|
+
else:
|
|
412
|
+
state = "pending"
|
|
413
|
+
status_label = "等待打包"
|
|
414
|
+
value = "PENDING"
|
|
415
|
+
summary = "尚未读取到当前交付 ZIP 的归档自检结果。"
|
|
416
|
+
next_action = "生成交付 ZIP 后刷新自检状态。"
|
|
417
|
+
|
|
418
|
+
return self._card(
|
|
419
|
+
"package_audit",
|
|
420
|
+
"交付 ZIP",
|
|
421
|
+
state,
|
|
422
|
+
status_label,
|
|
423
|
+
value,
|
|
424
|
+
summary,
|
|
425
|
+
[
|
|
426
|
+
f"entries={entries}; files_checked={checked}",
|
|
427
|
+
f"README={'ready' if package_audit.get('readme_present') else 'missing'}; manifest={'ready' if package_audit.get('manifest_present') else 'missing'}",
|
|
428
|
+
f"errors={errors}; warnings={warnings}; broken_images={broken_images}; broken_links={broken_links}",
|
|
429
|
+
],
|
|
430
|
+
next_action,
|
|
431
|
+
)
|
|
432
|
+
|
|
433
|
+
def _memory_seed_researcher_card(self, memory_seed: dict) -> dict:
|
|
434
|
+
import_ok = memory_seed.get("import_ok")
|
|
435
|
+
warnings = self._string_list(memory_seed.get("warnings"))
|
|
436
|
+
missing_roles = self._string_list(memory_seed.get("missing_roles"))
|
|
437
|
+
record_ready = bool(memory_seed.get("record_ready"))
|
|
438
|
+
summary_ready = bool(memory_seed.get("summary_ready"))
|
|
439
|
+
provider = memory_seed.get("provider") or "memory"
|
|
440
|
+
|
|
441
|
+
if import_ok is False:
|
|
442
|
+
state = "fail"
|
|
443
|
+
status_label = "导入失败"
|
|
444
|
+
value = f"{provider} FAILED"
|
|
445
|
+
summary = "已生成 memory seed 记录,但导入记忆供应商失败。"
|
|
446
|
+
next_action = "检查 provider 配置和导入日志后重新播种。"
|
|
447
|
+
elif import_ok is True and not warnings and not missing_roles:
|
|
448
|
+
state = "pass"
|
|
449
|
+
status_label = "已播种"
|
|
450
|
+
value = f"{provider} READY"
|
|
451
|
+
summary = "医学交付摘要已写入记忆供应商,可支持后续 recall。"
|
|
452
|
+
next_action = "后续相关研究可复用该交付记忆。"
|
|
453
|
+
elif record_ready or summary_ready:
|
|
454
|
+
state = "warning"
|
|
455
|
+
status_label = "待复核"
|
|
456
|
+
value = f"{provider} PARTIAL"
|
|
457
|
+
summary = "memory seed 文件已生成,但仍需确认导入、缺失角色或警告。"
|
|
458
|
+
next_action = "补齐缺失角色并复核播种摘要。"
|
|
459
|
+
else:
|
|
460
|
+
state = "pending"
|
|
461
|
+
status_label = "未播种"
|
|
462
|
+
value = "PENDING"
|
|
463
|
+
summary = "尚未为当前医学交付生成 memory seed。"
|
|
464
|
+
next_action = "运行 memory seed 步骤沉淀交付摘要。"
|
|
465
|
+
|
|
466
|
+
return self._card(
|
|
467
|
+
"memory_seed",
|
|
468
|
+
"记忆播种",
|
|
469
|
+
state,
|
|
470
|
+
status_label,
|
|
471
|
+
value,
|
|
472
|
+
summary,
|
|
473
|
+
[
|
|
474
|
+
f"record={'ready' if record_ready else 'missing'}; summary={'ready' if summary_ready else 'missing'}",
|
|
475
|
+
f"sources={memory_seed.get('source_file_count') or 0}; staged={memory_seed.get('staged_file_count') or 0}",
|
|
476
|
+
f"missing_roles={', '.join(missing_roles) if missing_roles else 'none'}",
|
|
477
|
+
*[f"warning={item}" for item in warnings[:3]],
|
|
478
|
+
],
|
|
479
|
+
next_action,
|
|
480
|
+
)
|
|
481
|
+
|
|
482
|
+
def _learning_candidate_researcher_card(self, learning_candidate: dict) -> dict:
|
|
483
|
+
brain_ingest_ok = learning_candidate.get("brain_ingest_ok")
|
|
484
|
+
promotable = bool(learning_candidate.get("promotable"))
|
|
485
|
+
record_ready = bool(learning_candidate.get("record_ready"))
|
|
486
|
+
summary_ready = bool(learning_candidate.get("summary_ready"))
|
|
487
|
+
recommended_action = learning_candidate.get("recommended_action") or "pending"
|
|
488
|
+
warnings = self._string_list(learning_candidate.get("warnings"))
|
|
489
|
+
review_status = str(learning_candidate.get("review_status") or "pending").strip().lower()
|
|
490
|
+
review_ready = bool(learning_candidate.get("review_ready"))
|
|
491
|
+
|
|
492
|
+
if brain_ingest_ok is False:
|
|
493
|
+
state = "fail"
|
|
494
|
+
status_label = "沉淀失败"
|
|
495
|
+
value = "FAILED"
|
|
496
|
+
summary = "learning candidate 生成后导入 Memory Brain 失败。"
|
|
497
|
+
next_action = "修复导入失败或证据缺口后重新生成候选。"
|
|
498
|
+
elif review_status == "approved":
|
|
499
|
+
state = "pass"
|
|
500
|
+
status_label = "已批准"
|
|
501
|
+
value = "APPROVED"
|
|
502
|
+
summary = "learning candidate 已通过 Memory Brain 人工审核,可作为长期医学研究记忆复用。"
|
|
503
|
+
next_action = "在后续医学研究中通过 Memory Brain recall 复用,并定期复核 stale 风险。"
|
|
504
|
+
elif review_status == "rejected":
|
|
505
|
+
state = "fail"
|
|
506
|
+
status_label = "已拒绝"
|
|
507
|
+
value = "REJECTED"
|
|
508
|
+
summary = "learning candidate 已被人工拒绝,不应进入长期知识或后续 recall。"
|
|
509
|
+
next_action = "依据拒绝原因补证据或保持排除,并重新生成候选后再审。"
|
|
510
|
+
elif review_status == "stale":
|
|
511
|
+
state = "warning"
|
|
512
|
+
status_label = "证据过期"
|
|
513
|
+
value = "STALE"
|
|
514
|
+
summary = "learning candidate 被标记为 stale,需要更新证据后再进入长期复用。"
|
|
515
|
+
next_action = "补跑 PubMed/OpenAlex、事实核查和统计审查后重新 restore 或 approve。"
|
|
516
|
+
elif review_status == "restored":
|
|
517
|
+
state = "warning"
|
|
518
|
+
status_label = "已恢复复审"
|
|
519
|
+
value = "RESTORED"
|
|
520
|
+
summary = "learning candidate 已从 rejected/stale 恢复,等待下一轮人工批准。"
|
|
521
|
+
next_action = "复核恢复原因、证据更新和质量门禁,再执行 approve/reject/stale。"
|
|
522
|
+
elif promotable:
|
|
523
|
+
state = "pass"
|
|
524
|
+
status_label = "可复审"
|
|
525
|
+
value = "READY"
|
|
526
|
+
summary = "本次医学交付已沉淀为可审阅的学习候选。"
|
|
527
|
+
next_action = "交给知识库或 Memory Brain 流程做人工复审。"
|
|
528
|
+
elif record_ready or summary_ready:
|
|
529
|
+
state = "warning"
|
|
530
|
+
status_label = "需补证据"
|
|
531
|
+
value = "REVIEW"
|
|
532
|
+
summary = "learning candidate 已生成,但还不适合直接提升为长期知识。"
|
|
533
|
+
next_action = "补齐证据、处理质量问题,再进入复审。"
|
|
534
|
+
else:
|
|
535
|
+
state = "pending"
|
|
536
|
+
status_label = "未生成"
|
|
537
|
+
value = "PENDING"
|
|
538
|
+
summary = "尚未为当前医学交付生成学习候选。"
|
|
539
|
+
next_action = "在质量和事实状态稳定后生成 learning candidate。"
|
|
540
|
+
|
|
541
|
+
card = self._card(
|
|
542
|
+
"learning_candidate",
|
|
543
|
+
"学习沉淀",
|
|
544
|
+
state,
|
|
545
|
+
status_label,
|
|
546
|
+
value,
|
|
547
|
+
summary,
|
|
548
|
+
[
|
|
549
|
+
f"candidate={learning_candidate.get('candidate_id') or 'missing'}",
|
|
550
|
+
f"record={'ready' if record_ready else 'missing'}; summary={'ready' if summary_ready else 'missing'}",
|
|
551
|
+
f"recommended={recommended_action}",
|
|
552
|
+
f"brain_node={learning_candidate.get('brain_node_id') or 'none'}",
|
|
553
|
+
f"review={'ready' if review_ready else 'pending'}; status={review_status}",
|
|
554
|
+
f"reviewer={learning_candidate.get('reviewer') or 'none'}",
|
|
555
|
+
*[f"warning={item}" for item in warnings[:3]],
|
|
556
|
+
],
|
|
557
|
+
next_action,
|
|
558
|
+
)
|
|
559
|
+
card["review_status"] = review_status
|
|
560
|
+
card["review_ready"] = review_ready
|
|
561
|
+
card["reviewer"] = learning_candidate.get("reviewer")
|
|
562
|
+
card["review_updated_at"] = learning_candidate.get("review_updated_at")
|
|
563
|
+
card["candidate_id"] = learning_candidate.get("candidate_id")
|
|
564
|
+
card["review_actions"] = ["approve", "reject", "stale", "restore"] if learning_candidate.get("candidate_id") else []
|
|
565
|
+
return card
|
|
566
|
+
|
|
567
|
+
def _researcher_readiness_cards(
|
|
568
|
+
self,
|
|
569
|
+
quality: dict,
|
|
570
|
+
fact: dict,
|
|
571
|
+
findings: list[object],
|
|
572
|
+
package_audit: dict,
|
|
573
|
+
memory_seed: dict,
|
|
574
|
+
learning_candidate: dict,
|
|
575
|
+
) -> list[dict]:
|
|
576
|
+
return [
|
|
577
|
+
self._quality_researcher_card(quality, findings),
|
|
578
|
+
self._fact_check_researcher_card(fact),
|
|
579
|
+
self._package_audit_researcher_card(package_audit),
|
|
580
|
+
self._memory_seed_researcher_card(memory_seed),
|
|
581
|
+
self._learning_candidate_researcher_card(learning_candidate),
|
|
582
|
+
]
|
|
583
|
+
|
|
584
|
+
def _attach_medical_research_os(self, status: dict) -> dict:
|
|
585
|
+
"""Attach the standalone MedSCALE medical research operating layer."""
|
|
586
|
+
if not isinstance(status, dict):
|
|
587
|
+
return status
|
|
588
|
+
summary = status.setdefault("summary", {})
|
|
589
|
+
if not isinstance(summary, dict):
|
|
590
|
+
summary = {}
|
|
591
|
+
status["summary"] = summary
|
|
592
|
+
|
|
593
|
+
readiness = self._dict_value(summary.get("delivery_readiness"))
|
|
594
|
+
researcher_cards = readiness.get("researcher_cards") if readiness else []
|
|
595
|
+
if not isinstance(researcher_cards, list):
|
|
596
|
+
researcher_cards = []
|
|
597
|
+
|
|
598
|
+
runtime_readiness = self._medical_runtime_readiness()
|
|
599
|
+
summary["medical_runtime_readiness"] = runtime_readiness
|
|
600
|
+
token_cost = self._token_cost_report()
|
|
601
|
+
|
|
602
|
+
signals = self._medical_os_signals(readiness, researcher_cards)
|
|
603
|
+
score = self._medical_os_score(signals)
|
|
604
|
+
status_label = self._medical_os_status(score, signals)
|
|
605
|
+
review_escalated = any(signal["state"] in {"fail", "warning"} for signal in signals)
|
|
606
|
+
missing_count = sum(1 for signal in signals if signal["state"] == "pending")
|
|
607
|
+
total_token_budget = 24000
|
|
608
|
+
assigned_tokens = 18000
|
|
609
|
+
|
|
610
|
+
summary["medical_research_os"] = {
|
|
611
|
+
"product": {
|
|
612
|
+
"name": "MedSCALE Research OS",
|
|
613
|
+
"cn_name": "MedSCALE 医研操作系统",
|
|
614
|
+
"tagline": "把医学研究 Agent 流程变成可审阅、可交付、可沉淀的证据操作系统。",
|
|
615
|
+
"source_upgrade": "MedSCALE native workflow",
|
|
616
|
+
},
|
|
617
|
+
"operating_model": {
|
|
618
|
+
"strategy": "medical-agent-collaboration-v1",
|
|
619
|
+
"mode": "review-escalated" if review_escalated else "multi-agent",
|
|
620
|
+
"status": status_label,
|
|
621
|
+
"readiness_score": score,
|
|
622
|
+
"ready_signals": sum(1 for signal in signals if signal["state"] == "pass"),
|
|
623
|
+
"warning_signals": sum(1 for signal in signals if signal["state"] == "warning"),
|
|
624
|
+
"missing_signals": missing_count,
|
|
625
|
+
"review_required": True,
|
|
626
|
+
"human_review_todo": readiness.get("human_review_todo") or [],
|
|
627
|
+
},
|
|
628
|
+
"loop_readiness": {
|
|
629
|
+
"score": score,
|
|
630
|
+
"signals": signals,
|
|
631
|
+
"recommendations": self._medical_os_recommendations(signals),
|
|
632
|
+
},
|
|
633
|
+
"research_team": {
|
|
634
|
+
"roles": self._medical_os_roles(signals),
|
|
635
|
+
"handoffs": self._medical_os_handoffs(),
|
|
636
|
+
"review_gates": self._medical_os_review_gates(signals),
|
|
637
|
+
"budget": {
|
|
638
|
+
"total_tokens": total_token_budget,
|
|
639
|
+
"assigned_tokens": assigned_tokens,
|
|
640
|
+
"reserve_tokens": total_token_budget - assigned_tokens,
|
|
641
|
+
"reserve_reason": "为人工复核、事实争议和补充检索保留上下文预算。",
|
|
642
|
+
},
|
|
643
|
+
},
|
|
644
|
+
"prompt_studio": {
|
|
645
|
+
"templates": self._medical_os_prompt_templates(),
|
|
646
|
+
"actions": ["copy_prompt", "download_prompt", "export_json"],
|
|
647
|
+
"safe_preview": True,
|
|
648
|
+
},
|
|
649
|
+
"memory_strategy": self._medical_os_memory_strategy(readiness),
|
|
650
|
+
"token_cost": token_cost,
|
|
651
|
+
"runtime_readiness": runtime_readiness,
|
|
652
|
+
"next_migrations": [
|
|
653
|
+
"把医学协作计划导出为独立 JSON,供 Dashboard、CLI 和后续 MCP 直接复用。",
|
|
654
|
+
"把 Prompt Studio 模板提升为 /api/prompts 医学分区,支持复制、下载和多角色 handoff。",
|
|
655
|
+
"新增 native medical doctor,检查 gbrain、PubMed/OpenAlex、包审计和图表引用。",
|
|
656
|
+
"持续记录文献评审、Agent review、fact check 和 memory settlement 的 token/cost,并在交付前复盘高成本环节。",
|
|
657
|
+
"把 Memory Brain 候选审核做成 approve/reject/stale/restore 的可审阅流程。",
|
|
658
|
+
],
|
|
659
|
+
}
|
|
660
|
+
return status
|
|
661
|
+
|
|
662
|
+
def _workspace_root(self) -> Path:
|
|
663
|
+
candidates = [self.base_dir, self.base_dir.parent]
|
|
664
|
+
for candidate in candidates:
|
|
665
|
+
if (candidate / ".scale").exists():
|
|
666
|
+
return candidate
|
|
667
|
+
for candidate in candidates:
|
|
668
|
+
if (candidate / "package.json").exists() and (candidate / "target-research-platform").exists():
|
|
669
|
+
return candidate
|
|
670
|
+
return self.base_dir
|
|
671
|
+
|
|
672
|
+
def _read_workspace_json(self, root: Path, relative_path: str) -> dict:
|
|
673
|
+
return self._read_json(root / relative_path)
|
|
674
|
+
|
|
675
|
+
def _model_usage_path(self) -> Path:
|
|
676
|
+
return self._workspace_root() / ".scale" / "model-usage" / "usage.jsonl"
|
|
677
|
+
|
|
678
|
+
@staticmethod
|
|
679
|
+
def _number_value(value: object, default: float = 0) -> float:
|
|
680
|
+
try:
|
|
681
|
+
parsed = float(value)
|
|
682
|
+
return parsed if parsed >= 0 else default
|
|
683
|
+
except (TypeError, ValueError):
|
|
684
|
+
return default
|
|
685
|
+
|
|
686
|
+
@staticmethod
|
|
687
|
+
def _usage_medical_lane(record: dict) -> str:
|
|
688
|
+
metadata = record.get("metadata") if isinstance(record.get("metadata"), dict) else {}
|
|
689
|
+
explicit = str(
|
|
690
|
+
metadata.get("medical_lane")
|
|
691
|
+
or metadata.get("phase")
|
|
692
|
+
or metadata.get("gate")
|
|
693
|
+
or ""
|
|
694
|
+
).lower()
|
|
695
|
+
haystack = " ".join([
|
|
696
|
+
explicit,
|
|
697
|
+
str(record.get("taskId") or ""),
|
|
698
|
+
str(record.get("sessionId") or ""),
|
|
699
|
+
str(record.get("model") or ""),
|
|
700
|
+
]).lower()
|
|
701
|
+
if any(token in haystack for token in ("literature", "pubmed", "openalex", "文献", "检索")):
|
|
702
|
+
return "literature"
|
|
703
|
+
if any(token in haystack for token in ("fact", "claim", "事实", "核查")):
|
|
704
|
+
return "fact_check"
|
|
705
|
+
if any(token in haystack for token in ("package", "audit", "delivery", "zip", "交付", "审计")):
|
|
706
|
+
return "package_audit"
|
|
707
|
+
if any(token in haystack for token in ("memory", "gbrain", "learning", "candidate", "记忆", "沉淀")):
|
|
708
|
+
return "memory"
|
|
709
|
+
if any(token in haystack for token in ("agent", "review", "handoff", "peer", "评审", "协作")):
|
|
710
|
+
return "agent_review"
|
|
711
|
+
return "workflow"
|
|
712
|
+
|
|
713
|
+
@staticmethod
|
|
714
|
+
def _usage_lane_label(lane: str) -> str:
|
|
715
|
+
return {
|
|
716
|
+
"literature": "Literature review",
|
|
717
|
+
"fact_check": "Medical fact check",
|
|
718
|
+
"package_audit": "Package audit",
|
|
719
|
+
"memory": "Memory settlement",
|
|
720
|
+
"agent_review": "Agent review",
|
|
721
|
+
"workflow": "Workflow",
|
|
722
|
+
}.get(lane, lane)
|
|
723
|
+
|
|
724
|
+
def _model_usage_records(self) -> list[dict]:
|
|
725
|
+
usage_path = self._model_usage_path()
|
|
726
|
+
if not usage_path.exists():
|
|
727
|
+
return []
|
|
728
|
+
records: list[dict] = []
|
|
729
|
+
for line in usage_path.read_text(encoding="utf-8").splitlines():
|
|
730
|
+
if not line.strip():
|
|
731
|
+
continue
|
|
732
|
+
try:
|
|
733
|
+
record = json.loads(line)
|
|
734
|
+
except json.JSONDecodeError:
|
|
735
|
+
continue
|
|
736
|
+
if isinstance(record, dict):
|
|
737
|
+
records.append(record)
|
|
738
|
+
return records
|
|
739
|
+
|
|
740
|
+
def _token_cost_report(self) -> dict:
|
|
741
|
+
usage_path = self._model_usage_path()
|
|
742
|
+
records = self._model_usage_records()
|
|
743
|
+
summary = {
|
|
744
|
+
"total_records": len(records),
|
|
745
|
+
"total_input_tokens": 0,
|
|
746
|
+
"total_output_tokens": 0,
|
|
747
|
+
"total_tokens": 0,
|
|
748
|
+
"cache_savings_tokens": 0,
|
|
749
|
+
"estimated_cost_usd": None,
|
|
750
|
+
}
|
|
751
|
+
lanes: dict[str, dict] = {}
|
|
752
|
+
estimated_cost: float | None = None
|
|
753
|
+
for record in records:
|
|
754
|
+
input_tokens = int(self._number_value(record.get("inputTokens")))
|
|
755
|
+
output_tokens = int(self._number_value(record.get("outputTokens")))
|
|
756
|
+
total_tokens = int(self._number_value(record.get("totalTokens"), input_tokens + output_tokens))
|
|
757
|
+
cache_savings = int(self._number_value(record.get("cacheSavingsTokens")))
|
|
758
|
+
cost_value = record.get("estimatedCostUsd")
|
|
759
|
+
cost = self._number_value(cost_value) if cost_value is not None else None
|
|
760
|
+
summary["total_input_tokens"] += input_tokens
|
|
761
|
+
summary["total_output_tokens"] += output_tokens
|
|
762
|
+
summary["total_tokens"] += total_tokens
|
|
763
|
+
summary["cache_savings_tokens"] += cache_savings
|
|
764
|
+
if cost is not None:
|
|
765
|
+
estimated_cost = (estimated_cost or 0) + cost
|
|
766
|
+
lane_key = self._usage_medical_lane(record)
|
|
767
|
+
lane = lanes.setdefault(lane_key, {
|
|
768
|
+
"key": lane_key,
|
|
769
|
+
"label": self._usage_lane_label(lane_key),
|
|
770
|
+
"records": 0,
|
|
771
|
+
"total_tokens": 0,
|
|
772
|
+
"cache_savings_tokens": 0,
|
|
773
|
+
"estimated_cost_usd": None,
|
|
774
|
+
})
|
|
775
|
+
lane["records"] += 1
|
|
776
|
+
lane["total_tokens"] += total_tokens
|
|
777
|
+
lane["cache_savings_tokens"] += cache_savings
|
|
778
|
+
if cost is not None:
|
|
779
|
+
lane["estimated_cost_usd"] = round((lane["estimated_cost_usd"] or 0) + cost, 6)
|
|
780
|
+
if estimated_cost is not None:
|
|
781
|
+
summary["estimated_cost_usd"] = round(estimated_cost, 6)
|
|
782
|
+
by_lane = sorted(lanes.values(), key=lambda item: (-item["total_tokens"], item["key"]))
|
|
783
|
+
recent_records = sorted(records, key=lambda item: str(item.get("timestamp") or ""), reverse=True)[:10]
|
|
784
|
+
return {
|
|
785
|
+
"schema": "medscale.token_cost.v1",
|
|
786
|
+
"status": "ready" if records else "pending",
|
|
787
|
+
"usage_path": str(usage_path),
|
|
788
|
+
"summary": summary,
|
|
789
|
+
"by_medical_lane": by_lane,
|
|
790
|
+
"recent_records": [
|
|
791
|
+
{
|
|
792
|
+
"timestamp": record.get("timestamp"),
|
|
793
|
+
"provider": record.get("provider"),
|
|
794
|
+
"model": record.get("model"),
|
|
795
|
+
"task_id": record.get("taskId"),
|
|
796
|
+
"total_tokens": record.get("totalTokens"),
|
|
797
|
+
"estimated_cost_usd": record.get("estimatedCostUsd"),
|
|
798
|
+
"medical_lane": self._usage_medical_lane(record),
|
|
799
|
+
}
|
|
800
|
+
for record in recent_records
|
|
801
|
+
],
|
|
802
|
+
"recommendations": [
|
|
803
|
+
"Review high-token medical lanes before expert handoff and package delivery.",
|
|
804
|
+
] if records else [
|
|
805
|
+
"Record token/cost usage after literature review, fact check, package audit, agent review, and memory settlement.",
|
|
806
|
+
],
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
@staticmethod
|
|
810
|
+
def _command_probe(command: str) -> dict:
|
|
811
|
+
binary = command.split()[0]
|
|
812
|
+
path = shutil.which(binary)
|
|
813
|
+
return {
|
|
814
|
+
"command": command,
|
|
815
|
+
"binary": binary,
|
|
816
|
+
"available": bool(path),
|
|
817
|
+
"path": path,
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
@staticmethod
|
|
821
|
+
def _runtime_card(
|
|
822
|
+
card_id: str,
|
|
823
|
+
title: str,
|
|
824
|
+
state: str,
|
|
825
|
+
status_label: str,
|
|
826
|
+
summary: str,
|
|
827
|
+
details: list[str],
|
|
828
|
+
next_action: str,
|
|
829
|
+
required: bool,
|
|
830
|
+
) -> dict:
|
|
831
|
+
return {
|
|
832
|
+
"id": card_id,
|
|
833
|
+
"title": title,
|
|
834
|
+
"state": state,
|
|
835
|
+
"status_label": status_label,
|
|
836
|
+
"summary": summary,
|
|
837
|
+
"details": details,
|
|
838
|
+
"next_action": next_action,
|
|
839
|
+
"required": required,
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
def _medical_runtime_readiness(self) -> dict:
|
|
843
|
+
root = self._workspace_root()
|
|
844
|
+
cards = [
|
|
845
|
+
self._runtime_memory_card(root),
|
|
846
|
+
self._runtime_code_intelligence_card(root),
|
|
847
|
+
self._runtime_cli_card(root),
|
|
848
|
+
self._runtime_skills_card(root),
|
|
849
|
+
self._runtime_mcp_card(root),
|
|
850
|
+
self._runtime_token_cost_card(root),
|
|
851
|
+
self._runtime_medical_workflow_card(root),
|
|
852
|
+
]
|
|
853
|
+
required_missing = [
|
|
854
|
+
detail
|
|
855
|
+
for card in cards
|
|
856
|
+
if card["required"] and card["state"] == "fail"
|
|
857
|
+
for detail in card["details"]
|
|
858
|
+
]
|
|
859
|
+
warning_count = sum(1 for card in cards if card["state"] == "warning")
|
|
860
|
+
fail_count = sum(1 for card in cards if card["state"] == "fail")
|
|
861
|
+
status = "ready"
|
|
862
|
+
if required_missing:
|
|
863
|
+
status = "blocked"
|
|
864
|
+
elif warning_count or fail_count:
|
|
865
|
+
status = "degraded"
|
|
866
|
+
return {
|
|
867
|
+
"schema": "medscale.runtime_readiness.v1",
|
|
868
|
+
"status": status,
|
|
869
|
+
"score": self._runtime_readiness_score(cards),
|
|
870
|
+
"workspace_root": str(root),
|
|
871
|
+
"required_missing": required_missing,
|
|
872
|
+
"warning_count": warning_count,
|
|
873
|
+
"fail_count": fail_count,
|
|
874
|
+
"cards": cards,
|
|
875
|
+
"recommendations": self._runtime_recommendations(cards),
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
def _runtime_memory_card(self, root: Path) -> dict:
|
|
879
|
+
config = self._read_workspace_json(root, ".scale/memory-providers.json")
|
|
880
|
+
providers = config.get("providers") if isinstance(config.get("providers"), list) else []
|
|
881
|
+
provider_by_id = {
|
|
882
|
+
str(provider.get("id")): provider
|
|
883
|
+
for provider in providers
|
|
884
|
+
if isinstance(provider, dict) and provider.get("id")
|
|
885
|
+
}
|
|
886
|
+
gbrain = self._dict_value(provider_by_id.get("gbrain"))
|
|
887
|
+
scale_local = self._dict_value(provider_by_id.get("scale-local"))
|
|
888
|
+
agentmemory = self._dict_value(provider_by_id.get("agentmemory"))
|
|
889
|
+
gbrain_probe = self._command_probe("gbrain")
|
|
890
|
+
routing = self._dict_value(config.get("routing"))
|
|
891
|
+
default_order = routing.get("defaultOrder") if isinstance(routing.get("defaultOrder"), list) else []
|
|
892
|
+
details = [
|
|
893
|
+
f"routing={routing.get('mode') or 'missing'}; order={','.join(default_order) if default_order else 'missing'}",
|
|
894
|
+
f"gbrain={'enabled' if gbrain.get('enabled') else 'disabled'}; command={'found' if gbrain_probe['available'] else 'missing'}; write={gbrain.get('writeMode') or 'unknown'}",
|
|
895
|
+
f"scale-local={'enabled' if scale_local.get('enabled') else 'disabled'}; write={scale_local.get('writeMode') or 'unknown'}",
|
|
896
|
+
f"agentmemory={'enabled' if agentmemory.get('enabled') else 'disabled'}",
|
|
897
|
+
]
|
|
898
|
+
if not config:
|
|
899
|
+
return self._runtime_card(
|
|
900
|
+
"third_party_memory",
|
|
901
|
+
"Third-party memory",
|
|
902
|
+
"warning",
|
|
903
|
+
"CONFIG MISSING",
|
|
904
|
+
"External memory routing is not configured; the native medical workflow can still run.",
|
|
905
|
+
details,
|
|
906
|
+
"Add a medical memory provider config when recall and memory review are needed.",
|
|
907
|
+
False,
|
|
908
|
+
)
|
|
909
|
+
if not gbrain.get("enabled") or not gbrain_probe["available"]:
|
|
910
|
+
return self._runtime_card(
|
|
911
|
+
"third_party_memory",
|
|
912
|
+
"Third-party memory",
|
|
913
|
+
"warning",
|
|
914
|
+
"GBRAIN OPTIONAL",
|
|
915
|
+
"gbrain is unavailable, so recall and memory review will stay local or pending.",
|
|
916
|
+
details,
|
|
917
|
+
"Install or enable gbrain only if this study needs cross-session memory recall.",
|
|
918
|
+
False,
|
|
919
|
+
)
|
|
920
|
+
return self._runtime_card(
|
|
921
|
+
"third_party_memory",
|
|
922
|
+
"Third-party memory",
|
|
923
|
+
"pass",
|
|
924
|
+
"GBRAIN READY",
|
|
925
|
+
"External-first memory routing is available for governed research recall.",
|
|
926
|
+
details,
|
|
927
|
+
"Next: expose provider recall and Memory Brain review transitions.",
|
|
928
|
+
False,
|
|
929
|
+
)
|
|
930
|
+
|
|
931
|
+
def _runtime_code_intelligence_card(self, root: Path) -> dict:
|
|
932
|
+
config = self._read_workspace_json(root, ".scale/code-intelligence.json")
|
|
933
|
+
codegraph_probe = self._command_probe("codegraph")
|
|
934
|
+
graphify_probe = self._command_probe("graphify")
|
|
935
|
+
graphify_manifest = root / "graphify-out" / "manifest.json"
|
|
936
|
+
codegraph_db = root / ".codegraph" / "codegraph.db"
|
|
937
|
+
details = [
|
|
938
|
+
f"codegraph_command={'found' if codegraph_probe['available'] else 'missing'}",
|
|
939
|
+
f"codegraph_db={'present' if codegraph_db.exists() else 'missing'}",
|
|
940
|
+
f"graphify_command={'found' if graphify_probe['available'] else 'missing'}",
|
|
941
|
+
f"graphify_manifest={'present' if graphify_manifest.exists() else 'missing'}",
|
|
942
|
+
f"config={'present' if config else 'missing'}",
|
|
943
|
+
]
|
|
944
|
+
if not codegraph_probe["available"]:
|
|
945
|
+
return self._runtime_card(
|
|
946
|
+
"code_intelligence",
|
|
947
|
+
"Code and knowledge graph",
|
|
948
|
+
"warning",
|
|
949
|
+
"CODEGRAPH OPTIONAL",
|
|
950
|
+
"CodeGraph is not available; MedSCALE will use manifest and file-based evidence instead.",
|
|
951
|
+
details,
|
|
952
|
+
"Install CodeGraph only if graph-backed research inspection is needed.",
|
|
953
|
+
False,
|
|
954
|
+
)
|
|
955
|
+
if not graphify_probe["available"] or not graphify_manifest.exists():
|
|
956
|
+
return self._runtime_card(
|
|
957
|
+
"code_intelligence",
|
|
958
|
+
"Code and knowledge graph",
|
|
959
|
+
"warning",
|
|
960
|
+
"GRAPH DEGRADED",
|
|
961
|
+
"Code intelligence is partially available, but Graphify is not fully initialized.",
|
|
962
|
+
details,
|
|
963
|
+
"Run graphify install/status and regenerate the artifact graph.",
|
|
964
|
+
False,
|
|
965
|
+
)
|
|
966
|
+
return self._runtime_card(
|
|
967
|
+
"code_intelligence",
|
|
968
|
+
"Code and knowledge graph",
|
|
969
|
+
"pass",
|
|
970
|
+
"GRAPH READY",
|
|
971
|
+
"CodeGraph and Graphify signals are available for research workflow inspection.",
|
|
972
|
+
details,
|
|
973
|
+
"Next: render evidence graph status in the MedSCALE dashboard.",
|
|
974
|
+
False,
|
|
975
|
+
)
|
|
976
|
+
|
|
977
|
+
def _runtime_cli_card(self, root: Path) -> dict:
|
|
978
|
+
probes = {
|
|
979
|
+
"python": self._command_probe("python"),
|
|
980
|
+
"uv": self._command_probe("uv"),
|
|
981
|
+
"node": self._command_probe("node"),
|
|
982
|
+
}
|
|
983
|
+
details = [
|
|
984
|
+
f"{name}={'found' if probe['available'] else 'missing'}"
|
|
985
|
+
for name, probe in probes.items()
|
|
986
|
+
]
|
|
987
|
+
if not probes["python"]["available"]:
|
|
988
|
+
return self._runtime_card(
|
|
989
|
+
"cli_runtime",
|
|
990
|
+
"Native medical runtime",
|
|
991
|
+
"fail",
|
|
992
|
+
"RUNTIME MISSING",
|
|
993
|
+
"Python is required to run the standalone medical research workflow.",
|
|
994
|
+
details,
|
|
995
|
+
"Restore Python 3.10+ PATH availability before running the medical platform.",
|
|
996
|
+
True,
|
|
997
|
+
)
|
|
998
|
+
if not probes["uv"]["available"]:
|
|
999
|
+
return self._runtime_card(
|
|
1000
|
+
"cli_runtime",
|
|
1001
|
+
"Native medical runtime",
|
|
1002
|
+
"warning",
|
|
1003
|
+
"PYTHON READY",
|
|
1004
|
+
"Python is available; uv is optional but recommended for reproducible medical dependencies.",
|
|
1005
|
+
details,
|
|
1006
|
+
"Use pip or install uv for faster isolated dependency setup.",
|
|
1007
|
+
True,
|
|
1008
|
+
)
|
|
1009
|
+
return self._runtime_card(
|
|
1010
|
+
"cli_runtime",
|
|
1011
|
+
"Native medical runtime",
|
|
1012
|
+
"pass",
|
|
1013
|
+
"PYTHON READY",
|
|
1014
|
+
"Native medical workflow runtime is available without an external governance CLI.",
|
|
1015
|
+
details,
|
|
1016
|
+
"Next: expose med-research os-plan, mcp doctor, and token report commands.",
|
|
1017
|
+
True,
|
|
1018
|
+
)
|
|
1019
|
+
|
|
1020
|
+
def _runtime_skills_card(self, root: Path) -> dict:
|
|
1021
|
+
config = self._read_workspace_json(root, ".scale/skills.json")
|
|
1022
|
+
domains = self._dict_value(config.get("domains"))
|
|
1023
|
+
required_medical_domains = [
|
|
1024
|
+
"medicalResearch",
|
|
1025
|
+
"medicalFactCheck",
|
|
1026
|
+
"medicalPackageAudit",
|
|
1027
|
+
"medicalMemory",
|
|
1028
|
+
]
|
|
1029
|
+
missing_domains = [domain for domain in required_medical_domains if domain not in domains]
|
|
1030
|
+
details = [
|
|
1031
|
+
f"policy={self._dict_value(config.get('policy')).get('mode') or 'missing'}",
|
|
1032
|
+
f"domain_count={len(domains)}",
|
|
1033
|
+
f"missing_medical_domains={','.join(missing_domains) if missing_domains else 'none'}",
|
|
1034
|
+
]
|
|
1035
|
+
if not config:
|
|
1036
|
+
return self._runtime_card(
|
|
1037
|
+
"medical_skills",
|
|
1038
|
+
"Medical skills",
|
|
1039
|
+
"warning",
|
|
1040
|
+
"SKILLS MISSING",
|
|
1041
|
+
"Skill governance is not configured for this workspace.",
|
|
1042
|
+
details,
|
|
1043
|
+
"Add medical skill domains for literature, fact check, package audit, and memory review.",
|
|
1044
|
+
False,
|
|
1045
|
+
)
|
|
1046
|
+
if missing_domains:
|
|
1047
|
+
return self._runtime_card(
|
|
1048
|
+
"medical_skills",
|
|
1049
|
+
"Medical skills",
|
|
1050
|
+
"warning",
|
|
1051
|
+
"MEDICAL DOMAINS MISSING",
|
|
1052
|
+
"Generic skill governance exists, but medical research roles are not first-class skill domains.",
|
|
1053
|
+
details,
|
|
1054
|
+
"Add MedSCALE skill domains and required evidence artifacts.",
|
|
1055
|
+
False,
|
|
1056
|
+
)
|
|
1057
|
+
return self._runtime_card(
|
|
1058
|
+
"medical_skills",
|
|
1059
|
+
"Medical skills",
|
|
1060
|
+
"pass",
|
|
1061
|
+
"SKILLS READY",
|
|
1062
|
+
"Medical research skill domains are configured.",
|
|
1063
|
+
details,
|
|
1064
|
+
"Next: bind each delivery gate to required skill evidence.",
|
|
1065
|
+
False,
|
|
1066
|
+
)
|
|
1067
|
+
|
|
1068
|
+
def _runtime_mcp_card(self, root: Path) -> dict:
|
|
1069
|
+
config_dir = root / "mcp-configs"
|
|
1070
|
+
config_entries = [
|
|
1071
|
+
item for item in config_dir.iterdir()
|
|
1072
|
+
if config_dir.exists() and not item.name.startswith("_")
|
|
1073
|
+
] if config_dir.exists() else []
|
|
1074
|
+
config_count = len(config_entries)
|
|
1075
|
+
server_yaml = root / ".scale" / "mcp-servers.yaml"
|
|
1076
|
+
details = [
|
|
1077
|
+
f"mcp_config_count={config_count}",
|
|
1078
|
+
f"mcp_servers_yaml={'present' if server_yaml.exists() else 'missing'}",
|
|
1079
|
+
f"memory_config={'present' if (config_dir / 'memory').exists() or (config_dir / 'memory.json').exists() else 'missing'}",
|
|
1080
|
+
f"playwright_config={'present' if (config_dir / 'playwright').exists() or (config_dir / 'playwright.json').exists() else 'missing'}",
|
|
1081
|
+
]
|
|
1082
|
+
if config_count == 0:
|
|
1083
|
+
return self._runtime_card(
|
|
1084
|
+
"mcp_surface",
|
|
1085
|
+
"MCP surface",
|
|
1086
|
+
"warning",
|
|
1087
|
+
"MCP MISSING",
|
|
1088
|
+
"No MCP configuration files were found for this workspace.",
|
|
1089
|
+
details,
|
|
1090
|
+
"Add MCP configs for memory, evidence manifests, browser checks, and medical retrieval.",
|
|
1091
|
+
False,
|
|
1092
|
+
)
|
|
1093
|
+
if not server_yaml.exists():
|
|
1094
|
+
return self._runtime_card(
|
|
1095
|
+
"mcp_surface",
|
|
1096
|
+
"MCP surface",
|
|
1097
|
+
"warning",
|
|
1098
|
+
"MCP PARTIAL",
|
|
1099
|
+
"MCP configs exist, but the workspace has no MedSCALE server map contract.",
|
|
1100
|
+
details,
|
|
1101
|
+
"Add a native MedSCALE MCP server map and expose status in the medical doctor.",
|
|
1102
|
+
False,
|
|
1103
|
+
)
|
|
1104
|
+
return self._runtime_card(
|
|
1105
|
+
"mcp_surface",
|
|
1106
|
+
"MCP surface",
|
|
1107
|
+
"pass",
|
|
1108
|
+
"MCP READY",
|
|
1109
|
+
"MCP configs and server map are present.",
|
|
1110
|
+
details,
|
|
1111
|
+
"Next: add medical literature and fact-check MCP routes.",
|
|
1112
|
+
False,
|
|
1113
|
+
)
|
|
1114
|
+
|
|
1115
|
+
def _runtime_token_cost_card(self, root: Path) -> dict:
|
|
1116
|
+
report = self._token_cost_report()
|
|
1117
|
+
summary = self._dict_value(report.get("summary"))
|
|
1118
|
+
details = [
|
|
1119
|
+
f"usage_path={report.get('usage_path')}",
|
|
1120
|
+
f"records={summary.get('total_records', 0)}",
|
|
1121
|
+
f"total_tokens={summary.get('total_tokens', 0)}",
|
|
1122
|
+
f"cache_savings_tokens={summary.get('cache_savings_tokens', 0)}",
|
|
1123
|
+
f"estimated_cost_usd={summary.get('estimated_cost_usd') if summary.get('estimated_cost_usd') is not None else 'unknown'}",
|
|
1124
|
+
]
|
|
1125
|
+
if not summary.get("total_records"):
|
|
1126
|
+
return self._runtime_card(
|
|
1127
|
+
"token_cost",
|
|
1128
|
+
"Token and cost ledger",
|
|
1129
|
+
"warning",
|
|
1130
|
+
"COST PENDING",
|
|
1131
|
+
"MedSCALE token/cost reporting is wired, but no medical model usage has been recorded yet.",
|
|
1132
|
+
details,
|
|
1133
|
+
"Record usage with medscale token record after literature review, fact check, package audit, agent review, and memory settlement.",
|
|
1134
|
+
False,
|
|
1135
|
+
)
|
|
1136
|
+
return self._runtime_card(
|
|
1137
|
+
"token_cost",
|
|
1138
|
+
"Token and cost ledger",
|
|
1139
|
+
"pass",
|
|
1140
|
+
"COST TRACKED",
|
|
1141
|
+
"MedSCALE has recorded model usage for cost and token review.",
|
|
1142
|
+
details,
|
|
1143
|
+
"Review medscale token report before package delivery and expert handoff.",
|
|
1144
|
+
False,
|
|
1145
|
+
)
|
|
1146
|
+
|
|
1147
|
+
def _runtime_medical_workflow_card(self, root: Path) -> dict:
|
|
1148
|
+
platform = root / "target-research-platform"
|
|
1149
|
+
if not platform.exists() and (self.base_dir / "main.py").exists():
|
|
1150
|
+
platform = self.base_dir
|
|
1151
|
+
main_py = platform / "main.py"
|
|
1152
|
+
requirements = platform / "requirements.txt"
|
|
1153
|
+
tests_dir = platform / "tests"
|
|
1154
|
+
dashboard_app = platform / "src" / "dashboard" / "app.py"
|
|
1155
|
+
details = [
|
|
1156
|
+
f"platform={'present' if platform.exists() else 'missing'}",
|
|
1157
|
+
f"main_py={'present' if main_py.exists() else 'missing'}",
|
|
1158
|
+
f"requirements={'present' if requirements.exists() else 'missing'}",
|
|
1159
|
+
f"tests={'present' if tests_dir.exists() else 'missing'}",
|
|
1160
|
+
f"dashboard_os={'present' if dashboard_app.exists() else 'missing'}",
|
|
1161
|
+
]
|
|
1162
|
+
if not platform.exists() or not main_py.exists():
|
|
1163
|
+
return self._runtime_card(
|
|
1164
|
+
"medical_workflow",
|
|
1165
|
+
"Medical workflow",
|
|
1166
|
+
"fail",
|
|
1167
|
+
"PLATFORM MISSING",
|
|
1168
|
+
"target-research-platform is required for MedSCALE Research OS.",
|
|
1169
|
+
details,
|
|
1170
|
+
"Restore target-research-platform before running the medical workflow.",
|
|
1171
|
+
True,
|
|
1172
|
+
)
|
|
1173
|
+
if not requirements.exists() or not tests_dir.exists():
|
|
1174
|
+
return self._runtime_card(
|
|
1175
|
+
"medical_workflow",
|
|
1176
|
+
"Medical workflow",
|
|
1177
|
+
"warning",
|
|
1178
|
+
"WORKFLOW PARTIAL",
|
|
1179
|
+
"The medical platform exists, but dependency or test evidence is incomplete.",
|
|
1180
|
+
details,
|
|
1181
|
+
"Add medical pytest coverage to the default verification profile.",
|
|
1182
|
+
True,
|
|
1183
|
+
)
|
|
1184
|
+
return self._runtime_card(
|
|
1185
|
+
"medical_workflow",
|
|
1186
|
+
"Medical workflow",
|
|
1187
|
+
"pass",
|
|
1188
|
+
"WORKFLOW READY",
|
|
1189
|
+
"Medical platform files, dependencies, and tests are discoverable.",
|
|
1190
|
+
details,
|
|
1191
|
+
"Next: include target-research-platform pytest in native medical verification.",
|
|
1192
|
+
True,
|
|
1193
|
+
)
|
|
1194
|
+
|
|
1195
|
+
@staticmethod
|
|
1196
|
+
def _runtime_readiness_score(cards: list[dict]) -> int:
|
|
1197
|
+
if not cards:
|
|
1198
|
+
return 0
|
|
1199
|
+
weights = {"pass": 100, "warning": 55, "pending": 25, "fail": 0}
|
|
1200
|
+
return round(sum(weights.get(str(card.get("state")), 0) for card in cards) / len(cards))
|
|
1201
|
+
|
|
1202
|
+
@staticmethod
|
|
1203
|
+
def _runtime_recommendations(cards: list[dict]) -> list[str]:
|
|
1204
|
+
items = []
|
|
1205
|
+
for card in cards:
|
|
1206
|
+
if card.get("state") in {"fail", "warning"} and card.get("next_action"):
|
|
1207
|
+
items.append(str(card["next_action"]))
|
|
1208
|
+
if not items:
|
|
1209
|
+
items.append("Run the native MedSCALE doctor before external expert review.")
|
|
1210
|
+
return items[:6]
|
|
1211
|
+
|
|
1212
|
+
def _medical_os_signals(self, readiness: dict, researcher_cards: list[object]) -> list[dict]:
|
|
1213
|
+
card_by_id = {
|
|
1214
|
+
str(card.get("id")): card
|
|
1215
|
+
for card in researcher_cards
|
|
1216
|
+
if isinstance(card, dict) and card.get("id")
|
|
1217
|
+
}
|
|
1218
|
+
literature = self._dict_value(readiness.get("literature_evidence"))
|
|
1219
|
+
literature_state = "pending"
|
|
1220
|
+
literature_summary = "等待文献评述和富集证据。"
|
|
1221
|
+
agent_or_llm = self._int_value(literature.get("agent_or_llm_count"))
|
|
1222
|
+
if literature.get("upgrade_needed"):
|
|
1223
|
+
literature_state = "warning"
|
|
1224
|
+
literature_summary = "仍含规则回退评述,建议升级到 Agent/LLM 深度评述。"
|
|
1225
|
+
elif agent_or_llm > 0:
|
|
1226
|
+
literature_state = "pass"
|
|
1227
|
+
literature_summary = "已有 Agent/LLM 文献评述信号。"
|
|
1228
|
+
elif self._int_value(literature.get("count")) > 0:
|
|
1229
|
+
literature_state = "warning"
|
|
1230
|
+
literature_summary = "有文献证据,但深度评述信号不足。"
|
|
1231
|
+
|
|
1232
|
+
signals = [
|
|
1233
|
+
self._signal_from_card(
|
|
1234
|
+
card_by_id.get("quality"),
|
|
1235
|
+
"quality_gate",
|
|
1236
|
+
"论文质量门禁",
|
|
1237
|
+
"pending",
|
|
1238
|
+
"等待质量门禁报告。",
|
|
1239
|
+
),
|
|
1240
|
+
self._signal_from_card(
|
|
1241
|
+
card_by_id.get("fact_check"),
|
|
1242
|
+
"fact_check",
|
|
1243
|
+
"医学事实核查",
|
|
1244
|
+
"pending",
|
|
1245
|
+
"等待事实核查报告。",
|
|
1246
|
+
),
|
|
1247
|
+
self._signal_from_card(
|
|
1248
|
+
card_by_id.get("package_audit"),
|
|
1249
|
+
"package_audit",
|
|
1250
|
+
"交付包审计",
|
|
1251
|
+
"pending",
|
|
1252
|
+
"等待 ZIP/manifest/README 自检。",
|
|
1253
|
+
),
|
|
1254
|
+
{
|
|
1255
|
+
"id": "literature_review",
|
|
1256
|
+
"label": "文献评述来源",
|
|
1257
|
+
"state": literature_state,
|
|
1258
|
+
"summary": literature_summary,
|
|
1259
|
+
"evidence": [
|
|
1260
|
+
f"reviews={literature.get('count', 0)}",
|
|
1261
|
+
f"agent_or_llm={agent_or_llm}",
|
|
1262
|
+
f"rule_based={literature.get('rule_based_count', 0)}",
|
|
1263
|
+
],
|
|
1264
|
+
},
|
|
1265
|
+
self._signal_from_card(
|
|
1266
|
+
card_by_id.get("memory_seed"),
|
|
1267
|
+
"memory_seed",
|
|
1268
|
+
"gbrain 记忆播种",
|
|
1269
|
+
"pending",
|
|
1270
|
+
"等待 memory seed。",
|
|
1271
|
+
),
|
|
1272
|
+
self._signal_from_card(
|
|
1273
|
+
card_by_id.get("learning_candidate"),
|
|
1274
|
+
"learning_candidate",
|
|
1275
|
+
"学习候选沉淀",
|
|
1276
|
+
"pending",
|
|
1277
|
+
"等待 learning candidate。",
|
|
1278
|
+
),
|
|
1279
|
+
]
|
|
1280
|
+
return signals
|
|
1281
|
+
|
|
1282
|
+
@staticmethod
|
|
1283
|
+
def _signal_from_card(
|
|
1284
|
+
card: dict | None,
|
|
1285
|
+
signal_id: str,
|
|
1286
|
+
label: str,
|
|
1287
|
+
default_state: str,
|
|
1288
|
+
default_summary: str,
|
|
1289
|
+
) -> dict:
|
|
1290
|
+
if not card:
|
|
1291
|
+
return {
|
|
1292
|
+
"id": signal_id,
|
|
1293
|
+
"label": label,
|
|
1294
|
+
"state": default_state,
|
|
1295
|
+
"summary": default_summary,
|
|
1296
|
+
"evidence": [],
|
|
1297
|
+
}
|
|
1298
|
+
details = card.get("details") if isinstance(card.get("details"), list) else []
|
|
1299
|
+
return {
|
|
1300
|
+
"id": signal_id,
|
|
1301
|
+
"label": label,
|
|
1302
|
+
"state": str(card.get("state") or default_state),
|
|
1303
|
+
"summary": str(card.get("summary") or default_summary),
|
|
1304
|
+
"evidence": [str(item) for item in details[:4]],
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
@staticmethod
|
|
1308
|
+
def _medical_os_score(signals: list[dict]) -> int:
|
|
1309
|
+
if not signals:
|
|
1310
|
+
return 0
|
|
1311
|
+
weights = {"pass": 100, "warning": 60, "pending": 0, "fail": 0}
|
|
1312
|
+
total = sum(weights.get(str(signal.get("state")), 0) for signal in signals)
|
|
1313
|
+
return round(total / len(signals))
|
|
1314
|
+
|
|
1315
|
+
@staticmethod
|
|
1316
|
+
def _medical_os_status(score: int, signals: list[dict]) -> str:
|
|
1317
|
+
if any(signal.get("state") == "fail" for signal in signals):
|
|
1318
|
+
return "blocked"
|
|
1319
|
+
if score >= 90:
|
|
1320
|
+
return "ready-for-expert-review"
|
|
1321
|
+
if score >= 60:
|
|
1322
|
+
return "needs-researcher-review"
|
|
1323
|
+
return "insufficient-evidence"
|
|
1324
|
+
|
|
1325
|
+
@staticmethod
|
|
1326
|
+
def _medical_os_recommendations(signals: list[dict]) -> list[str]:
|
|
1327
|
+
recommendations = []
|
|
1328
|
+
for signal in signals:
|
|
1329
|
+
state = signal.get("state")
|
|
1330
|
+
if state == "fail":
|
|
1331
|
+
recommendations.append(f"先修复 {signal.get('label')} 的阻断项,再进入外部审阅。")
|
|
1332
|
+
elif state == "warning":
|
|
1333
|
+
recommendations.append(f"复核 {signal.get('label')} 的警告项,并补充可追溯证据。")
|
|
1334
|
+
elif state == "pending":
|
|
1335
|
+
recommendations.append(f"补齐 {signal.get('label')},让研究者视图形成闭环。")
|
|
1336
|
+
if not recommendations:
|
|
1337
|
+
recommendations.append("进入医学专家复审,并保留人工结论与证据包。")
|
|
1338
|
+
return recommendations[:5]
|
|
1339
|
+
|
|
1340
|
+
@staticmethod
|
|
1341
|
+
def _medical_os_roles(signals: list[dict]) -> list[dict]:
|
|
1342
|
+
state_by_id = {signal.get("id"): signal.get("state") for signal in signals}
|
|
1343
|
+
return [
|
|
1344
|
+
{
|
|
1345
|
+
"id": "principal_investigator",
|
|
1346
|
+
"name": "Principal Investigator",
|
|
1347
|
+
"responsibility": "orchestrator",
|
|
1348
|
+
"state": "warning",
|
|
1349
|
+
"reason": "负责医学边界、研究问题、人工最终结论和伦理/投稿约束。",
|
|
1350
|
+
},
|
|
1351
|
+
{
|
|
1352
|
+
"id": "literature_reviewer",
|
|
1353
|
+
"name": "Literature Reviewer",
|
|
1354
|
+
"responsibility": "specialist",
|
|
1355
|
+
"state": state_by_id.get("literature_review", "pending"),
|
|
1356
|
+
"reason": "负责 PubMed/OpenAlex/Agent 评述证据和引用质量。",
|
|
1357
|
+
},
|
|
1358
|
+
{
|
|
1359
|
+
"id": "manuscript_quality_reviewer",
|
|
1360
|
+
"name": "Manuscript Quality Reviewer",
|
|
1361
|
+
"responsibility": "verifier",
|
|
1362
|
+
"state": state_by_id.get("quality_gate", "pending"),
|
|
1363
|
+
"reason": "负责 IMRaD 结构、图表、质量门禁和投递前检查。",
|
|
1364
|
+
},
|
|
1365
|
+
{
|
|
1366
|
+
"id": "medical_fact_checker",
|
|
1367
|
+
"name": "Medical Fact Checker",
|
|
1368
|
+
"responsibility": "reviewer",
|
|
1369
|
+
"state": state_by_id.get("fact_check", "pending"),
|
|
1370
|
+
"reason": "负责 PMID、统计数字、医学声明和图表引用核查。",
|
|
1371
|
+
},
|
|
1372
|
+
{
|
|
1373
|
+
"id": "delivery_steward",
|
|
1374
|
+
"name": "Delivery Steward",
|
|
1375
|
+
"responsibility": "releaser",
|
|
1376
|
+
"state": state_by_id.get("package_audit", "pending"),
|
|
1377
|
+
"reason": "负责 ZIP、manifest、README、下载物和归档一致性。",
|
|
1378
|
+
},
|
|
1379
|
+
{
|
|
1380
|
+
"id": "memory_curator",
|
|
1381
|
+
"name": "Memory Curator",
|
|
1382
|
+
"responsibility": "specialist",
|
|
1383
|
+
"state": state_by_id.get("memory_seed", "pending"),
|
|
1384
|
+
"reason": "负责 gbrain 播种、learning candidate 和长期知识复审。",
|
|
1385
|
+
},
|
|
1386
|
+
]
|
|
1387
|
+
|
|
1388
|
+
@staticmethod
|
|
1389
|
+
def _medical_os_handoffs() -> list[dict]:
|
|
1390
|
+
return [
|
|
1391
|
+
{
|
|
1392
|
+
"from": "literature_reviewer",
|
|
1393
|
+
"to": "medical_fact_checker",
|
|
1394
|
+
"artifact": "literature evidence pack",
|
|
1395
|
+
"exit_criteria": ["关键 claims 有 PMID 或可追溯来源", "规则回退评述已标记"],
|
|
1396
|
+
},
|
|
1397
|
+
{
|
|
1398
|
+
"from": "medical_fact_checker",
|
|
1399
|
+
"to": "manuscript_quality_reviewer",
|
|
1400
|
+
"artifact": "fact check report",
|
|
1401
|
+
"exit_criteria": ["failed claims 为 0", "unverified claims 有人工复核待办"],
|
|
1402
|
+
},
|
|
1403
|
+
{
|
|
1404
|
+
"from": "manuscript_quality_reviewer",
|
|
1405
|
+
"to": "delivery_steward",
|
|
1406
|
+
"artifact": "quality gate report",
|
|
1407
|
+
"exit_criteria": ["质量门禁通过或阻断项已记录", "图表与元数据可归档"],
|
|
1408
|
+
},
|
|
1409
|
+
{
|
|
1410
|
+
"from": "delivery_steward",
|
|
1411
|
+
"to": "memory_curator",
|
|
1412
|
+
"artifact": "delivery manifest",
|
|
1413
|
+
"exit_criteria": ["ZIP 自检通过", "memory seed 源文件列表完整"],
|
|
1414
|
+
},
|
|
1415
|
+
]
|
|
1416
|
+
|
|
1417
|
+
@staticmethod
|
|
1418
|
+
def _medical_os_review_gates(signals: list[dict]) -> list[dict]:
|
|
1419
|
+
return [
|
|
1420
|
+
{
|
|
1421
|
+
"id": signal.get("id"),
|
|
1422
|
+
"owner": {
|
|
1423
|
+
"quality_gate": "manuscript_quality_reviewer",
|
|
1424
|
+
"fact_check": "medical_fact_checker",
|
|
1425
|
+
"package_audit": "delivery_steward",
|
|
1426
|
+
"literature_review": "literature_reviewer",
|
|
1427
|
+
"memory_seed": "memory_curator",
|
|
1428
|
+
"learning_candidate": "memory_curator",
|
|
1429
|
+
}.get(str(signal.get("id")), "principal_investigator"),
|
|
1430
|
+
"required": True,
|
|
1431
|
+
"state": signal.get("state"),
|
|
1432
|
+
"reason": signal.get("summary"),
|
|
1433
|
+
}
|
|
1434
|
+
for signal in signals
|
|
1435
|
+
]
|
|
1436
|
+
|
|
1437
|
+
@staticmethod
|
|
1438
|
+
def _medical_os_prompt_templates() -> list[dict]:
|
|
1439
|
+
return [
|
|
1440
|
+
{
|
|
1441
|
+
"id": "med-target-scout",
|
|
1442
|
+
"title": "靶点研究任务定义",
|
|
1443
|
+
"phase": "define",
|
|
1444
|
+
"role": "principal_investigator",
|
|
1445
|
+
"output": "研究问题、边界声明、目标疾病和证据需求。",
|
|
1446
|
+
},
|
|
1447
|
+
{
|
|
1448
|
+
"id": "med-literature-review",
|
|
1449
|
+
"title": "PubMed/OpenAlex 文献评述",
|
|
1450
|
+
"phase": "research",
|
|
1451
|
+
"role": "literature_reviewer",
|
|
1452
|
+
"output": "PMID、MeSH、引用数、证据等级和争议点。",
|
|
1453
|
+
},
|
|
1454
|
+
{
|
|
1455
|
+
"id": "med-manuscript-imrad",
|
|
1456
|
+
"title": "医学论文 IMRaD 草稿",
|
|
1457
|
+
"phase": "build",
|
|
1458
|
+
"role": "manuscript_quality_reviewer",
|
|
1459
|
+
"output": "结构化论文草稿、图表嵌入和投稿边界。",
|
|
1460
|
+
},
|
|
1461
|
+
{
|
|
1462
|
+
"id": "med-fact-check",
|
|
1463
|
+
"title": "医学事实核查",
|
|
1464
|
+
"phase": "verify",
|
|
1465
|
+
"role": "medical_fact_checker",
|
|
1466
|
+
"output": "claims、PMID、数字、图表引用和未验证项。",
|
|
1467
|
+
},
|
|
1468
|
+
{
|
|
1469
|
+
"id": "med-package-audit",
|
|
1470
|
+
"title": "交付包审计",
|
|
1471
|
+
"phase": "ship",
|
|
1472
|
+
"role": "delivery_steward",
|
|
1473
|
+
"output": "ZIP、manifest、README、断链和下载物一致性。",
|
|
1474
|
+
},
|
|
1475
|
+
{
|
|
1476
|
+
"id": "med-memory-learning",
|
|
1477
|
+
"title": "记忆播种与学习候选",
|
|
1478
|
+
"phase": "learn",
|
|
1479
|
+
"role": "memory_curator",
|
|
1480
|
+
"output": "gbrain seed、learning candidate 和人工复审建议。",
|
|
1481
|
+
},
|
|
1482
|
+
]
|
|
1483
|
+
|
|
1484
|
+
@staticmethod
|
|
1485
|
+
def _medical_os_memory_strategy(readiness: dict) -> dict:
|
|
1486
|
+
memory_seed = readiness.get("memory_seed") if isinstance(readiness.get("memory_seed"), dict) else {}
|
|
1487
|
+
learning_candidate = readiness.get("learning_candidate") if isinstance(readiness.get("learning_candidate"), dict) else {}
|
|
1488
|
+
review_status = str(learning_candidate.get("review_status") or "pending").strip().lower()
|
|
1489
|
+
if review_status in {"approved", "rejected", "stale", "restored"}:
|
|
1490
|
+
candidate_state = review_status
|
|
1491
|
+
elif learning_candidate.get("promotable"):
|
|
1492
|
+
candidate_state = "promotable"
|
|
1493
|
+
else:
|
|
1494
|
+
candidate_state = "review-required"
|
|
1495
|
+
return {
|
|
1496
|
+
"provider": memory_seed.get("provider") or "gbrain",
|
|
1497
|
+
"routing": "gbrain-only",
|
|
1498
|
+
"seed_state": "ready" if memory_seed.get("import_ok") is True else "pending",
|
|
1499
|
+
"candidate_state": candidate_state,
|
|
1500
|
+
"review_status": review_status,
|
|
1501
|
+
"review_ready": bool(learning_candidate.get("review_ready")),
|
|
1502
|
+
"record_path": memory_seed.get("record_path"),
|
|
1503
|
+
"candidate_id": learning_candidate.get("candidate_id"),
|
|
1504
|
+
"review_path": learning_candidate.get("review_path"),
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1507
|
+
def build_medical_research_os_plan(self) -> dict:
|
|
1508
|
+
"""Build a read-only MedSCALE OS plan packet for dashboard copy/download."""
|
|
1509
|
+
status = self.load_status()
|
|
1510
|
+
summary = self._dict_value(status.get("summary"))
|
|
1511
|
+
med_os = self._dict_value(summary.get("medical_research_os"))
|
|
1512
|
+
product = self._dict_value(med_os.get("product"))
|
|
1513
|
+
operating = self._dict_value(med_os.get("operating_model"))
|
|
1514
|
+
loop = self._dict_value(med_os.get("loop_readiness"))
|
|
1515
|
+
team = self._dict_value(med_os.get("research_team"))
|
|
1516
|
+
prompt_studio = self._dict_value(med_os.get("prompt_studio"))
|
|
1517
|
+
runtime_readiness = self._dict_value(med_os.get("runtime_readiness") or summary.get("medical_runtime_readiness"))
|
|
1518
|
+
token_cost = self._dict_value(med_os.get("token_cost"))
|
|
1519
|
+
templates = prompt_studio.get("templates") if isinstance(prompt_studio.get("templates"), list) else []
|
|
1520
|
+
roles = team.get("roles") if isinstance(team.get("roles"), list) else []
|
|
1521
|
+
handoffs = team.get("handoffs") if isinstance(team.get("handoffs"), list) else []
|
|
1522
|
+
review_gates = team.get("review_gates") if isinstance(team.get("review_gates"), list) else []
|
|
1523
|
+
signals = loop.get("signals") if isinstance(loop.get("signals"), list) else []
|
|
1524
|
+
recommendations = loop.get("recommendations") if isinstance(loop.get("recommendations"), list) else []
|
|
1525
|
+
target = str(status.get("target") or "").strip() or "{{target}}"
|
|
1526
|
+
disease = str(status.get("disease") or "").strip() or "{{disease}}"
|
|
1527
|
+
|
|
1528
|
+
return {
|
|
1529
|
+
"schema": "medscale.research_os.plan.v1",
|
|
1530
|
+
"generated_at": datetime.now().isoformat(),
|
|
1531
|
+
"run": {
|
|
1532
|
+
"run_id": status.get("run_id"),
|
|
1533
|
+
"status": status.get("status"),
|
|
1534
|
+
"target": target,
|
|
1535
|
+
"disease": disease,
|
|
1536
|
+
"updated_at": status.get("updated_at"),
|
|
1537
|
+
},
|
|
1538
|
+
"product": {
|
|
1539
|
+
"name": product.get("name") or "MedSCALE Research OS",
|
|
1540
|
+
"cn_name": product.get("cn_name") or "MedSCALE 医研操作系统",
|
|
1541
|
+
"source_upgrade": product.get("source_upgrade") or "MedSCALE native workflow",
|
|
1542
|
+
"tagline": product.get("tagline"),
|
|
1543
|
+
},
|
|
1544
|
+
"agent_collaboration_plan": {
|
|
1545
|
+
"strategy": operating.get("strategy") or "medical-agent-collaboration-v1",
|
|
1546
|
+
"mode": operating.get("mode") or "multi-agent",
|
|
1547
|
+
"roles": roles,
|
|
1548
|
+
"edges": [
|
|
1549
|
+
{
|
|
1550
|
+
"from": handoff.get("from"),
|
|
1551
|
+
"to": handoff.get("to"),
|
|
1552
|
+
"type": "handoff",
|
|
1553
|
+
"reason": handoff.get("artifact"),
|
|
1554
|
+
}
|
|
1555
|
+
for handoff in handoffs
|
|
1556
|
+
if isinstance(handoff, dict)
|
|
1557
|
+
],
|
|
1558
|
+
"handoffs": handoffs,
|
|
1559
|
+
"review_gates": review_gates,
|
|
1560
|
+
"budget": team.get("budget") or {},
|
|
1561
|
+
"summary": {
|
|
1562
|
+
"total_roles": len(roles),
|
|
1563
|
+
"required_roles": len([role for role in roles if isinstance(role, dict)]),
|
|
1564
|
+
"review_gate_count": len(review_gates),
|
|
1565
|
+
"handoff_count": len(handoffs),
|
|
1566
|
+
"multi_agent_recommended": True,
|
|
1567
|
+
"review_escalated": operating.get("mode") == "review-escalated",
|
|
1568
|
+
},
|
|
1569
|
+
"recommendations": recommendations,
|
|
1570
|
+
},
|
|
1571
|
+
"agent_loop_readiness": {
|
|
1572
|
+
"status": operating.get("status") or "insufficient-evidence",
|
|
1573
|
+
"score": operating.get("readiness_score") or loop.get("score") or 0,
|
|
1574
|
+
"summary": {
|
|
1575
|
+
"ready_signals": operating.get("ready_signals") or 0,
|
|
1576
|
+
"warning_signals": operating.get("warning_signals") or 0,
|
|
1577
|
+
"missing_signals": operating.get("missing_signals") or 0,
|
|
1578
|
+
"recommendations": recommendations,
|
|
1579
|
+
},
|
|
1580
|
+
"signals": signals,
|
|
1581
|
+
},
|
|
1582
|
+
"prompt_studio": {
|
|
1583
|
+
"safe_preview": True,
|
|
1584
|
+
"templates": [
|
|
1585
|
+
{
|
|
1586
|
+
**template,
|
|
1587
|
+
"copy_prompt": self._medical_prompt_text(template, target, disease),
|
|
1588
|
+
"download_filename": f"medscale-{template.get('id', 'prompt')}.md",
|
|
1589
|
+
}
|
|
1590
|
+
for template in templates
|
|
1591
|
+
if isinstance(template, dict)
|
|
1592
|
+
],
|
|
1593
|
+
},
|
|
1594
|
+
"memory_strategy": med_os.get("memory_strategy") or {},
|
|
1595
|
+
"token_cost": token_cost,
|
|
1596
|
+
"runtime_readiness": runtime_readiness,
|
|
1597
|
+
"safety": {
|
|
1598
|
+
"read_only": True,
|
|
1599
|
+
"shell_execution": False,
|
|
1600
|
+
"medical_boundary": "This plan supports research workflow governance only. It is not clinical advice and requires human expert review.",
|
|
1601
|
+
},
|
|
1602
|
+
"next_migrations": med_os.get("next_migrations") or [],
|
|
1603
|
+
}
|
|
1604
|
+
|
|
1605
|
+
@staticmethod
|
|
1606
|
+
def _medical_prompt_text(template: dict, target: str, disease: str) -> str:
|
|
1607
|
+
title = template.get("title") or template.get("id") or "MedSCALE prompt"
|
|
1608
|
+
role = template.get("role") or "medical_research_agent"
|
|
1609
|
+
phase = template.get("phase") or "research"
|
|
1610
|
+
output = template.get("output") or "Produce a traceable research artifact."
|
|
1611
|
+
return "\n".join([
|
|
1612
|
+
f"# {title}",
|
|
1613
|
+
"",
|
|
1614
|
+
f"Product: MedSCALE Research OS",
|
|
1615
|
+
f"Role: {role}",
|
|
1616
|
+
f"Phase: {phase}",
|
|
1617
|
+
f"Target: {target}",
|
|
1618
|
+
f"Disease: {disease}",
|
|
1619
|
+
"",
|
|
1620
|
+
"## Objective",
|
|
1621
|
+
str(output),
|
|
1622
|
+
"",
|
|
1623
|
+
"## Evidence Rules",
|
|
1624
|
+
"- Cite PMID, DOI, dataset, figure, or manifest path when available.",
|
|
1625
|
+
"- Separate observed evidence from inference.",
|
|
1626
|
+
"- Mark unresolved medical claims as requiring human expert review.",
|
|
1627
|
+
"- Do not present draft research output as clinical advice.",
|
|
1628
|
+
"",
|
|
1629
|
+
"## Output",
|
|
1630
|
+
"- Summary",
|
|
1631
|
+
"- Evidence table",
|
|
1632
|
+
"- Risks and unresolved claims",
|
|
1633
|
+
"- Next action for the responsible reviewer",
|
|
1634
|
+
])
|
|
1635
|
+
|
|
1636
|
+
@staticmethod
|
|
1637
|
+
def _literature_evidence(manifest: dict) -> dict:
|
|
1638
|
+
literature = manifest.get("literature") or {}
|
|
1639
|
+
enrichment = literature.get("enrichment") or {}
|
|
1640
|
+
agent_cache_count = int(literature.get("agent_cache_count", 0) or 0)
|
|
1641
|
+
llm_count = int(literature.get("llm_count", 0) or 0)
|
|
1642
|
+
rule_based_count = int(literature.get("rule_based_count", 0) or 0)
|
|
1643
|
+
return {
|
|
1644
|
+
"count": literature.get("count", 0),
|
|
1645
|
+
"agent_cache_count": agent_cache_count,
|
|
1646
|
+
"llm_count": llm_count,
|
|
1647
|
+
"rule_based_count": rule_based_count,
|
|
1648
|
+
"agent_or_llm_count": agent_cache_count + llm_count,
|
|
1649
|
+
"upgrade_needed": rule_based_count > 0 and (agent_cache_count + llm_count) == 0,
|
|
1650
|
+
"sources": enrichment.get("sources") or [],
|
|
1651
|
+
"mesh_count": enrichment.get("mesh_count", 0),
|
|
1652
|
+
"citation_count_available": enrichment.get("citation_count_available", 0),
|
|
1653
|
+
"cache_used": bool(enrichment.get("cache_used")),
|
|
1654
|
+
"cache_hit_count": enrichment.get("cache_hit_count", 0),
|
|
1655
|
+
}
|
|
1656
|
+
|
|
1657
|
+
@staticmethod
|
|
1658
|
+
def _manifest_file_path(manifest: dict, role: str) -> str | None:
|
|
1659
|
+
for item in manifest.get("files") or []:
|
|
1660
|
+
if item.get("role") == role and item.get("path"):
|
|
1661
|
+
return str(item["path"])
|
|
1662
|
+
return None
|
|
1663
|
+
|
|
1664
|
+
def _literature_review_examples(self, manifest: dict) -> list[dict]:
|
|
1665
|
+
meta_path = self._resolve_artifact_path(self._manifest_file_path(manifest, "manuscript_meta"))
|
|
1666
|
+
meta = self._read_json(meta_path)
|
|
1667
|
+
reviews = meta.get("literature_reviews") or []
|
|
1668
|
+
examples: list[dict] = []
|
|
1669
|
+
for item in reviews[:6]:
|
|
1670
|
+
examples.append({
|
|
1671
|
+
"index": item.get("index"),
|
|
1672
|
+
"pmid": item.get("pmid"),
|
|
1673
|
+
"title": item.get("title"),
|
|
1674
|
+
"study_design": item.get("study_design"),
|
|
1675
|
+
"evidence_level": item.get("evidence_level"),
|
|
1676
|
+
"source": item.get("source"),
|
|
1677
|
+
})
|
|
1678
|
+
if examples:
|
|
1679
|
+
return examples
|
|
1680
|
+
|
|
1681
|
+
task_path = self._resolve_artifact_path(self._manifest_file_path(manifest, "agent_review_tasks"))
|
|
1682
|
+
tasks = self._read_json(task_path)
|
|
1683
|
+
papers = tasks.get("papers") or []
|
|
1684
|
+
for item in papers[:6]:
|
|
1685
|
+
examples.append({
|
|
1686
|
+
"index": item.get("index"),
|
|
1687
|
+
"pmid": item.get("pmid"),
|
|
1688
|
+
"title": item.get("title"),
|
|
1689
|
+
"study_design": item.get("study_design"),
|
|
1690
|
+
"evidence_level": None,
|
|
1691
|
+
"source": "pending_agent_review",
|
|
1692
|
+
})
|
|
1693
|
+
return examples
|
|
1694
|
+
|
|
1695
|
+
def _literature_review_files(self, manifest: dict) -> dict:
|
|
1696
|
+
task_path = self._resolve_artifact_path(self._manifest_file_path(manifest, "agent_review_tasks"))
|
|
1697
|
+
cache_path = self._resolve_artifact_path(self._manifest_file_path(manifest, "agent_reviews"))
|
|
1698
|
+
return {
|
|
1699
|
+
"task_path": str(task_path) if task_path and task_path.exists() else None,
|
|
1700
|
+
"cache_path": str(cache_path) if cache_path and cache_path.exists() else None,
|
|
1701
|
+
}
|
|
1702
|
+
|
|
1703
|
+
@staticmethod
|
|
1704
|
+
def _string_list(value: object) -> list[str]:
|
|
1705
|
+
if not isinstance(value, list):
|
|
1706
|
+
return []
|
|
1707
|
+
items: list[str] = []
|
|
1708
|
+
for item in value:
|
|
1709
|
+
text = str(item).strip()
|
|
1710
|
+
if text:
|
|
1711
|
+
items.append(text)
|
|
1712
|
+
return items
|
|
1713
|
+
|
|
1714
|
+
def _memory_seed_status(self, manifest_path: Path | None) -> dict:
|
|
1715
|
+
if not manifest_path:
|
|
1716
|
+
return {
|
|
1717
|
+
"record_path": None,
|
|
1718
|
+
"summary_path": None,
|
|
1719
|
+
"provider": None,
|
|
1720
|
+
"generated_at": None,
|
|
1721
|
+
"record_ready": False,
|
|
1722
|
+
"summary_ready": False,
|
|
1723
|
+
"import_ok": None,
|
|
1724
|
+
"source_file_count": 0,
|
|
1725
|
+
"staged_file_count": 0,
|
|
1726
|
+
"missing_roles": [],
|
|
1727
|
+
"warnings": [],
|
|
1728
|
+
}
|
|
1729
|
+
|
|
1730
|
+
slug = manifest_path.name.replace("_delivery_manifest.json", "")
|
|
1731
|
+
record_path = manifest_path.with_name(f"{slug}_memory_seed.json")
|
|
1732
|
+
summary_path = manifest_path.with_name(f"{slug}_memory_summary.md")
|
|
1733
|
+
record = self._read_json(record_path)
|
|
1734
|
+
|
|
1735
|
+
summary_from_record = record.get("summary_path") if record else None
|
|
1736
|
+
if isinstance(summary_from_record, str) and summary_from_record.strip():
|
|
1737
|
+
resolved_summary = self._resolve_artifact_path(summary_from_record)
|
|
1738
|
+
if resolved_summary:
|
|
1739
|
+
summary_path = resolved_summary
|
|
1740
|
+
|
|
1741
|
+
source_files = self._string_list(record.get("source_files") if record else None)
|
|
1742
|
+
staged_files = self._string_list(record.get("staged_files") if record else None)
|
|
1743
|
+
missing_roles = self._string_list(record.get("missing_roles") if record else None)
|
|
1744
|
+
warnings = self._string_list(record.get("warnings") if record else None)
|
|
1745
|
+
provider_raw = str(record.get("provider") or "").strip() if record else ""
|
|
1746
|
+
generated_at_raw = str(record.get("generated_at") or "").strip() if record else ""
|
|
1747
|
+
import_ok_raw = record.get("import_ok") if record else None
|
|
1748
|
+
|
|
1749
|
+
return {
|
|
1750
|
+
"record_path": str(record_path) if record_path.exists() else None,
|
|
1751
|
+
"summary_path": str(summary_path) if summary_path.exists() else None,
|
|
1752
|
+
"provider": provider_raw or None,
|
|
1753
|
+
"generated_at": generated_at_raw or None,
|
|
1754
|
+
"record_ready": record_path.exists(),
|
|
1755
|
+
"summary_ready": summary_path.exists(),
|
|
1756
|
+
"import_ok": import_ok_raw if isinstance(import_ok_raw, bool) else None,
|
|
1757
|
+
"source_file_count": len(source_files),
|
|
1758
|
+
"staged_file_count": len(staged_files),
|
|
1759
|
+
"missing_roles": missing_roles,
|
|
1760
|
+
"warnings": warnings,
|
|
1761
|
+
}
|
|
1762
|
+
|
|
1763
|
+
def _learning_candidate_review_status(self, record: dict, record_path: Path) -> dict:
|
|
1764
|
+
candidate_id = str(record.get("candidate_id") or "").strip()
|
|
1765
|
+
candidate_path = self._resolve_artifact_path(record.get("candidate_path"))
|
|
1766
|
+
review_path = self._resolve_artifact_path(
|
|
1767
|
+
record.get("review_path") or record.get("reviewPath")
|
|
1768
|
+
)
|
|
1769
|
+
if not review_path and candidate_path:
|
|
1770
|
+
review_path = candidate_path.with_name(f"{candidate_path.stem}.review.json")
|
|
1771
|
+
if not review_path and candidate_id:
|
|
1772
|
+
review_path = self.base_dir / ".scale" / "memory" / "learning-candidates" / f"{candidate_id}.review.json"
|
|
1773
|
+
if not review_path:
|
|
1774
|
+
review_path = record_path.with_name(f"{record_path.stem}.review.json")
|
|
1775
|
+
|
|
1776
|
+
review = self._read_json(review_path)
|
|
1777
|
+
review_status = str(review.get("status") or "pending").strip().lower() if review else "pending"
|
|
1778
|
+
review_markdown_path = self._resolve_artifact_path(
|
|
1779
|
+
review.get("reviewMarkdownPath") or review.get("review_markdown_path")
|
|
1780
|
+
) if review else None
|
|
1781
|
+
if not review_markdown_path and review_path:
|
|
1782
|
+
review_markdown_path = review_path.with_suffix(".md")
|
|
1783
|
+
|
|
1784
|
+
return {
|
|
1785
|
+
"review_status": review_status,
|
|
1786
|
+
"review_ready": bool(review),
|
|
1787
|
+
"review_path": str(review_path) if review_path and review_path.exists() else None,
|
|
1788
|
+
"review_expected_path": str(review_path) if review_path else None,
|
|
1789
|
+
"review_markdown_path": str(review_markdown_path) if review_markdown_path and review_markdown_path.exists() else None,
|
|
1790
|
+
"reviewer": review.get("reviewer") if review else None,
|
|
1791
|
+
"review_reason": review.get("reason") if review else None,
|
|
1792
|
+
"review_updated_at": (review.get("updatedAt") or review.get("updated_at")) if review else None,
|
|
1793
|
+
"review_node_id": (review.get("nodeId") or review.get("node_id")) if review else None,
|
|
1794
|
+
"review_node_status": (review.get("nodeStatus") or review.get("node_status")) if review else None,
|
|
1795
|
+
"promoted": bool(review.get("promoted")) if review else False,
|
|
1796
|
+
"review_warnings": self._string_list(review.get("warnings") if review else None),
|
|
1797
|
+
}
|
|
1798
|
+
|
|
1799
|
+
def _learning_candidate_status(self, manifest_path: Path | None) -> dict:
|
|
1800
|
+
if not manifest_path:
|
|
1801
|
+
return {
|
|
1802
|
+
"candidate_id": None,
|
|
1803
|
+
"record_path": None,
|
|
1804
|
+
"summary_path": None,
|
|
1805
|
+
"candidate_path": None,
|
|
1806
|
+
"candidate_markdown_path": None,
|
|
1807
|
+
"generated_at": None,
|
|
1808
|
+
"recommended_action": None,
|
|
1809
|
+
"promotable": False,
|
|
1810
|
+
"brain_ingest_ok": None,
|
|
1811
|
+
"brain_node_id": None,
|
|
1812
|
+
"record_ready": False,
|
|
1813
|
+
"summary_ready": False,
|
|
1814
|
+
"warnings": [],
|
|
1815
|
+
"review_status": "pending",
|
|
1816
|
+
"review_ready": False,
|
|
1817
|
+
"review_path": None,
|
|
1818
|
+
"review_expected_path": None,
|
|
1819
|
+
"review_markdown_path": None,
|
|
1820
|
+
"reviewer": None,
|
|
1821
|
+
"review_reason": None,
|
|
1822
|
+
"review_updated_at": None,
|
|
1823
|
+
"review_node_id": None,
|
|
1824
|
+
"review_node_status": None,
|
|
1825
|
+
"promoted": False,
|
|
1826
|
+
"review_warnings": [],
|
|
1827
|
+
}
|
|
1828
|
+
|
|
1829
|
+
slug = manifest_path.name.replace("_delivery_manifest.json", "")
|
|
1830
|
+
record_path = manifest_path.with_name(f"{slug}_memory_learning_candidate.json")
|
|
1831
|
+
summary_path = manifest_path.with_name(f"{slug}_memory_learning_candidate.md")
|
|
1832
|
+
record = self._read_json(record_path)
|
|
1833
|
+
review_status = self._learning_candidate_review_status(record, record_path) if record else {
|
|
1834
|
+
"review_status": "pending",
|
|
1835
|
+
"review_ready": False,
|
|
1836
|
+
"review_path": None,
|
|
1837
|
+
"review_expected_path": None,
|
|
1838
|
+
"review_markdown_path": None,
|
|
1839
|
+
"reviewer": None,
|
|
1840
|
+
"review_reason": None,
|
|
1841
|
+
"review_updated_at": None,
|
|
1842
|
+
"review_node_id": None,
|
|
1843
|
+
"review_node_status": None,
|
|
1844
|
+
"promoted": False,
|
|
1845
|
+
"review_warnings": [],
|
|
1846
|
+
}
|
|
1847
|
+
|
|
1848
|
+
return {
|
|
1849
|
+
"candidate_id": record.get("candidate_id"),
|
|
1850
|
+
"record_path": str(record_path) if record_path.exists() else None,
|
|
1851
|
+
"summary_path": str(summary_path) if summary_path.exists() else None,
|
|
1852
|
+
"candidate_path": record.get("candidate_path"),
|
|
1853
|
+
"candidate_markdown_path": record.get("candidate_markdown_path"),
|
|
1854
|
+
"generated_at": record.get("generated_at"),
|
|
1855
|
+
"recommended_action": record.get("recommended_action"),
|
|
1856
|
+
"promotable": bool(record.get("promotable")),
|
|
1857
|
+
"brain_ingest_ok": record.get("brain_ingest_ok") if isinstance(record.get("brain_ingest_ok"), bool) else None,
|
|
1858
|
+
"brain_node_id": record.get("brain_node_id"),
|
|
1859
|
+
"record_ready": record_path.exists(),
|
|
1860
|
+
"summary_ready": summary_path.exists(),
|
|
1861
|
+
"warnings": self._string_list(record.get("warnings") if record else None),
|
|
1862
|
+
**review_status,
|
|
1863
|
+
}
|
|
1864
|
+
|
|
1865
|
+
def start_pipeline(self, payload: dict) -> tuple[bool, str]:
|
|
1866
|
+
with self._lock:
|
|
1867
|
+
if self._process and self._process.poll() is None:
|
|
1868
|
+
return False, "A pipeline run is already in progress."
|
|
1869
|
+
|
|
1870
|
+
run_id = uuid.uuid4().hex[:10]
|
|
1871
|
+
target = payload.get("target", "CDK4").strip() or "CDK4"
|
|
1872
|
+
disease = payload.get("disease", "breast cancer").strip() or "breast cancer"
|
|
1873
|
+
cancers_raw = payload.get("cancers", "BRCA,LUAD")
|
|
1874
|
+
cancers = [item.strip() for item in cancers_raw.split(",") if item.strip()]
|
|
1875
|
+
if not cancers:
|
|
1876
|
+
cancers = ["BRCA", "LUAD"]
|
|
1877
|
+
modality = payload.get("modality", "small_molecule").strip() or "small_molecule"
|
|
1878
|
+
risk_level = payload.get("risk_level", "medium").strip() or "medium"
|
|
1879
|
+
|
|
1880
|
+
cmd = [
|
|
1881
|
+
sys.executable,
|
|
1882
|
+
"main.py",
|
|
1883
|
+
"full",
|
|
1884
|
+
"--target",
|
|
1885
|
+
target,
|
|
1886
|
+
"--disease",
|
|
1887
|
+
disease,
|
|
1888
|
+
"--modality",
|
|
1889
|
+
modality,
|
|
1890
|
+
"--risk-level",
|
|
1891
|
+
risk_level,
|
|
1892
|
+
"--output",
|
|
1893
|
+
"./output",
|
|
1894
|
+
"--cache",
|
|
1895
|
+
"./cache",
|
|
1896
|
+
]
|
|
1897
|
+
if cancers:
|
|
1898
|
+
cmd.append("--cancers")
|
|
1899
|
+
cmd.extend(cancers)
|
|
1900
|
+
|
|
1901
|
+
bootstrap_code = (
|
|
1902
|
+
"import argparse, uuid; "
|
|
1903
|
+
"from pathlib import Path; "
|
|
1904
|
+
"import main as pipeline_main; "
|
|
1905
|
+
f"tracker = pipeline_main.LiveStatusTracker(Path(r'{self.status_file}')); "
|
|
1906
|
+
"args = argparse.Namespace("
|
|
1907
|
+
f"target={target!r}, disease={disease!r}, cancers={cancers!r}, "
|
|
1908
|
+
"ensg=None, modality="
|
|
1909
|
+
f"{modality!r}, risk_level={risk_level!r}, output='./output', cache='./cache', json=False, "
|
|
1910
|
+
f"_tracker=tracker, _run_id={run_id!r}"
|
|
1911
|
+
"); "
|
|
1912
|
+
"pipeline_main.run_full_pipeline(args)"
|
|
1913
|
+
)
|
|
1914
|
+
process_cmd = [sys.executable, "-c", bootstrap_code]
|
|
1915
|
+
|
|
1916
|
+
self.tracker.start_run(
|
|
1917
|
+
{
|
|
1918
|
+
"run_id": run_id,
|
|
1919
|
+
"target": target,
|
|
1920
|
+
"disease": disease,
|
|
1921
|
+
"cancers": cancers,
|
|
1922
|
+
"modality": modality,
|
|
1923
|
+
"risk_level": risk_level,
|
|
1924
|
+
"output_root": "./output",
|
|
1925
|
+
}
|
|
1926
|
+
)
|
|
1927
|
+
self.tracker.log(f"Spawning pipeline process: {' '.join(cmd)}")
|
|
1928
|
+
self._process = subprocess.Popen(
|
|
1929
|
+
process_cmd,
|
|
1930
|
+
cwd=self.base_dir,
|
|
1931
|
+
stdout=subprocess.PIPE,
|
|
1932
|
+
stderr=subprocess.STDOUT,
|
|
1933
|
+
text=True,
|
|
1934
|
+
encoding="utf-8",
|
|
1935
|
+
errors="replace",
|
|
1936
|
+
)
|
|
1937
|
+
threading.Thread(target=self._pump_logs, daemon=True).start()
|
|
1938
|
+
return True, run_id
|
|
1939
|
+
|
|
1940
|
+
def _pump_logs(self) -> None:
|
|
1941
|
+
process = self._process
|
|
1942
|
+
if not process or not process.stdout:
|
|
1943
|
+
return
|
|
1944
|
+
for line in process.stdout:
|
|
1945
|
+
message = line.rstrip()
|
|
1946
|
+
if message:
|
|
1947
|
+
self.tracker.log(message)
|
|
1948
|
+
return_code = process.wait()
|
|
1949
|
+
if return_code != 0:
|
|
1950
|
+
self.tracker.fail_run(f"Pipeline process exited with code {return_code}")
|
|
1951
|
+
with self._lock:
|
|
1952
|
+
self._process = None
|
|
1953
|
+
|
|
1954
|
+
def review_memory_candidate(self, payload: dict) -> tuple[bool, dict, HTTPStatus]:
|
|
1955
|
+
"""Run the native MedSCALE Memory Brain review command from the dashboard."""
|
|
1956
|
+
if not isinstance(payload, dict):
|
|
1957
|
+
payload = {}
|
|
1958
|
+
action = str(payload.get("action") or "").strip().lower()
|
|
1959
|
+
if action not in {"approve", "reject", "stale", "restore"}:
|
|
1960
|
+
return False, {"ok": False, "error": "action must be approve, reject, stale, or restore"}, HTTPStatus.BAD_REQUEST
|
|
1961
|
+
|
|
1962
|
+
candidate_id = str(payload.get("candidate_id") or payload.get("candidateId") or "").strip()
|
|
1963
|
+
reviewer = str(payload.get("reviewer") or "18766-dashboard").strip() or "18766-dashboard"
|
|
1964
|
+
reason = str(payload.get("reason") or "").strip()
|
|
1965
|
+
scope = str(payload.get("scope") or "").strip()
|
|
1966
|
+
project_root = self._medscale_project_root()
|
|
1967
|
+
command = self._medscale_memory_review_command(project_root)
|
|
1968
|
+
if not command:
|
|
1969
|
+
return False, {
|
|
1970
|
+
"ok": False,
|
|
1971
|
+
"error": "MedSCALE CLI entrypoint is unavailable. Run npm install and npm run build, or use npm run medscale.",
|
|
1972
|
+
}, HTTPStatus.SERVICE_UNAVAILABLE
|
|
1973
|
+
|
|
1974
|
+
args = [
|
|
1975
|
+
*command,
|
|
1976
|
+
"memory-review",
|
|
1977
|
+
action,
|
|
1978
|
+
"--dir",
|
|
1979
|
+
str(project_root),
|
|
1980
|
+
"--reviewer",
|
|
1981
|
+
reviewer,
|
|
1982
|
+
"--json",
|
|
1983
|
+
]
|
|
1984
|
+
if candidate_id:
|
|
1985
|
+
args.extend(["--candidate-id", candidate_id])
|
|
1986
|
+
if reason:
|
|
1987
|
+
args.extend(["--reason", reason])
|
|
1988
|
+
if scope:
|
|
1989
|
+
args.extend(["--scope", scope])
|
|
1990
|
+
|
|
1991
|
+
try:
|
|
1992
|
+
completed = subprocess.run(
|
|
1993
|
+
args,
|
|
1994
|
+
cwd=str(project_root),
|
|
1995
|
+
capture_output=True,
|
|
1996
|
+
text=True,
|
|
1997
|
+
encoding="utf-8",
|
|
1998
|
+
errors="replace",
|
|
1999
|
+
timeout=60,
|
|
2000
|
+
)
|
|
2001
|
+
except FileNotFoundError as exc:
|
|
2002
|
+
return False, {"ok": False, "error": f"MedSCALE command not found: {exc}"}, HTTPStatus.SERVICE_UNAVAILABLE
|
|
2003
|
+
except subprocess.TimeoutExpired:
|
|
2004
|
+
return False, {"ok": False, "error": "Memory review command timed out."}, HTTPStatus.REQUEST_TIMEOUT
|
|
2005
|
+
|
|
2006
|
+
stdout = (completed.stdout or "").strip()
|
|
2007
|
+
stderr = (completed.stderr or "").strip()
|
|
2008
|
+
report: dict = {}
|
|
2009
|
+
if stdout:
|
|
2010
|
+
try:
|
|
2011
|
+
parsed = json.loads(stdout)
|
|
2012
|
+
report = parsed if isinstance(parsed, dict) else {}
|
|
2013
|
+
except json.JSONDecodeError:
|
|
2014
|
+
return False, {
|
|
2015
|
+
"ok": False,
|
|
2016
|
+
"error": "Memory review command returned non-JSON output.",
|
|
2017
|
+
"stdout": stdout[-1000:],
|
|
2018
|
+
"stderr": stderr[-1000:],
|
|
2019
|
+
}, HTTPStatus.INTERNAL_SERVER_ERROR
|
|
2020
|
+
|
|
2021
|
+
ok = completed.returncode == 0 and report.get("ok") is not False
|
|
2022
|
+
response = {
|
|
2023
|
+
"ok": ok,
|
|
2024
|
+
"action": action,
|
|
2025
|
+
"candidate_id": report.get("candidateId") or report.get("candidate_id") or candidate_id or None,
|
|
2026
|
+
"status": report.get("status"),
|
|
2027
|
+
"node_id": report.get("nodeId") or report.get("node_id"),
|
|
2028
|
+
"node_status": report.get("nodeStatus") or report.get("node_status"),
|
|
2029
|
+
"review_path": report.get("reviewPath") or report.get("review_path"),
|
|
2030
|
+
"review_markdown_path": report.get("reviewMarkdownPath") or report.get("review_markdown_path"),
|
|
2031
|
+
"promoted": bool(report.get("promoted")),
|
|
2032
|
+
"warnings": report.get("warnings") if isinstance(report.get("warnings"), list) else [],
|
|
2033
|
+
"report": report,
|
|
2034
|
+
}
|
|
2035
|
+
if not ok:
|
|
2036
|
+
response["error"] = stderr or "Memory review command failed."
|
|
2037
|
+
return False, response, HTTPStatus.CONFLICT
|
|
2038
|
+
|
|
2039
|
+
self.tracker.log(
|
|
2040
|
+
f"Memory review {action}: {response['candidate_id'] or '<latest>'} -> {response.get('status') or 'unknown'}"
|
|
2041
|
+
)
|
|
2042
|
+
return True, response, HTTPStatus.OK
|
|
2043
|
+
|
|
2044
|
+
def _medscale_project_root(self) -> Path:
|
|
2045
|
+
candidates = [self.base_dir.parent, self.base_dir]
|
|
2046
|
+
for candidate in candidates:
|
|
2047
|
+
if (candidate / "package.json").exists() and (candidate / "src" / "api" / "medscale.ts").exists():
|
|
2048
|
+
return candidate.resolve()
|
|
2049
|
+
return self.base_dir.resolve()
|
|
2050
|
+
|
|
2051
|
+
@staticmethod
|
|
2052
|
+
def _medscale_memory_review_command(project_root: Path) -> list[str]:
|
|
2053
|
+
built_entry = project_root / "dist" / "api" / "medscale.js"
|
|
2054
|
+
if built_entry.exists():
|
|
2055
|
+
return ["node", str(built_entry)]
|
|
2056
|
+
if (project_root / "package.json").exists():
|
|
2057
|
+
npm = "npm.cmd" if sys.platform.startswith("win") else "npm"
|
|
2058
|
+
return [npm, "run", "--silent", "medscale", "--"]
|
|
2059
|
+
return []
|
|
2060
|
+
|
|
2061
|
+
|
|
2062
|
+
class DashboardHandler(SimpleHTTPRequestHandler):
|
|
2063
|
+
def __init__(self, *args, dashboard_state: DashboardState, **kwargs):
|
|
2064
|
+
self.dashboard_state = dashboard_state
|
|
2065
|
+
super().__init__(*args, directory=str(dashboard_state.base_dir / "src" / "dashboard" / "static"), **kwargs)
|
|
2066
|
+
|
|
2067
|
+
def do_GET(self) -> None:
|
|
2068
|
+
parsed = urlparse(self.path)
|
|
2069
|
+
if parsed.path == "/favicon.ico":
|
|
2070
|
+
self.send_response(HTTPStatus.NO_CONTENT)
|
|
2071
|
+
self.send_header("Content-Length", "0")
|
|
2072
|
+
self.end_headers()
|
|
2073
|
+
return
|
|
2074
|
+
if parsed.path == "/api/status":
|
|
2075
|
+
self._send_json(self.dashboard_state.load_status())
|
|
2076
|
+
return
|
|
2077
|
+
if parsed.path == "/api/medical-research-os":
|
|
2078
|
+
self._send_json(self.dashboard_state.build_medical_research_os_plan())
|
|
2079
|
+
return
|
|
2080
|
+
if parsed.path == "/api/health":
|
|
2081
|
+
self._send_json({"ok": True})
|
|
2082
|
+
return
|
|
2083
|
+
# 新增: 产物数据 API(支持读取 stage JSON / markdown / milestones)
|
|
2084
|
+
if parsed.path == "/api/artifact":
|
|
2085
|
+
params = parse_qs(parsed.query)
|
|
2086
|
+
artifact_path = params.get("path", [None])[0]
|
|
2087
|
+
if artifact_path:
|
|
2088
|
+
self._serve_artifact(artifact_path)
|
|
2089
|
+
return
|
|
2090
|
+
self._send_json({"error": "Missing path parameter"}, status=HTTPStatus.BAD_REQUEST)
|
|
2091
|
+
return
|
|
2092
|
+
# 新增: 产物文件下载
|
|
2093
|
+
if parsed.path == "/api/download":
|
|
2094
|
+
params = parse_qs(parsed.query)
|
|
2095
|
+
file_path = params.get("path", [None])[0]
|
|
2096
|
+
inline = params.get("inline", ["0"])[0] in {"1", "true", "yes"}
|
|
2097
|
+
if file_path:
|
|
2098
|
+
self._serve_download(file_path, inline=inline)
|
|
2099
|
+
return
|
|
2100
|
+
self._send_json({"error": "Missing path parameter"}, status=HTTPStatus.BAD_REQUEST)
|
|
2101
|
+
return
|
|
2102
|
+
# 研究者交付物(精选 + 高级数据)
|
|
2103
|
+
if parsed.path == "/api/artifacts":
|
|
2104
|
+
self._list_artifacts()
|
|
2105
|
+
return
|
|
2106
|
+
# 靶点对比(自动发现 stage1-4 指标)
|
|
2107
|
+
if parsed.path == "/api/compare":
|
|
2108
|
+
self._send_json(build_comparison_report(self.dashboard_state.output_dir))
|
|
2109
|
+
return
|
|
2110
|
+
# 在线分析报告(从磁盘发现,不依赖 completed 状态)
|
|
2111
|
+
if parsed.path == "/api/reports":
|
|
2112
|
+
status = self.dashboard_state.load_status()
|
|
2113
|
+
self._send_json(build_report_bundle(
|
|
2114
|
+
self.dashboard_state.output_dir,
|
|
2115
|
+
current_target=status.get("target") or None,
|
|
2116
|
+
current_disease=status.get("disease") or None,
|
|
2117
|
+
))
|
|
2118
|
+
return
|
|
2119
|
+
# JSON 预览摘要
|
|
2120
|
+
if parsed.path == "/api/preview-summary":
|
|
2121
|
+
params = parse_qs(parsed.query)
|
|
2122
|
+
artifact_path = params.get("path", [None])[0]
|
|
2123
|
+
if not artifact_path:
|
|
2124
|
+
self._send_json({"error": "Missing path"}, status=HTTPStatus.BAD_REQUEST)
|
|
2125
|
+
return
|
|
2126
|
+
self._preview_summary(artifact_path)
|
|
2127
|
+
return
|
|
2128
|
+
if parsed.path == "/":
|
|
2129
|
+
self.path = "/index.html"
|
|
2130
|
+
super().do_GET()
|
|
2131
|
+
|
|
2132
|
+
def do_POST(self) -> None:
|
|
2133
|
+
parsed = urlparse(self.path)
|
|
2134
|
+
if parsed.path == "/api/run":
|
|
2135
|
+
self._handle_run_request()
|
|
2136
|
+
return
|
|
2137
|
+
if parsed.path == "/api/memory-review":
|
|
2138
|
+
self._handle_memory_review_request()
|
|
2139
|
+
return
|
|
2140
|
+
self.send_error(HTTPStatus.NOT_FOUND, "Unknown endpoint")
|
|
2141
|
+
|
|
2142
|
+
def _read_json_body(self) -> dict:
|
|
2143
|
+
length = int(self.headers.get("Content-Length", "0"))
|
|
2144
|
+
raw = self.rfile.read(length).decode("utf-8") if length else "{}"
|
|
2145
|
+
try:
|
|
2146
|
+
payload = json.loads(raw)
|
|
2147
|
+
return payload if isinstance(payload, dict) else {}
|
|
2148
|
+
except json.JSONDecodeError:
|
|
2149
|
+
return {}
|
|
2150
|
+
|
|
2151
|
+
def _handle_run_request(self) -> None:
|
|
2152
|
+
payload = self._read_json_body()
|
|
2153
|
+
ok, result = self.dashboard_state.start_pipeline(payload)
|
|
2154
|
+
if ok:
|
|
2155
|
+
self._send_json({"ok": True, "run_id": result})
|
|
2156
|
+
else:
|
|
2157
|
+
self._send_json({"ok": False, "error": result}, status=HTTPStatus.CONFLICT)
|
|
2158
|
+
|
|
2159
|
+
def _handle_memory_review_request(self) -> None:
|
|
2160
|
+
ok, payload, status = self.dashboard_state.review_memory_candidate(self._read_json_body())
|
|
2161
|
+
self._send_json(payload, status=status)
|
|
2162
|
+
|
|
2163
|
+
def _send_json(self, payload: dict, status: HTTPStatus = HTTPStatus.OK) -> None:
|
|
2164
|
+
body = json.dumps(payload, ensure_ascii=False).encode("utf-8")
|
|
2165
|
+
self.send_response(status)
|
|
2166
|
+
self.send_header("Content-Type", "application/json; charset=utf-8")
|
|
2167
|
+
self.send_header("Content-Length", str(len(body)))
|
|
2168
|
+
self.end_headers()
|
|
2169
|
+
self.wfile.write(body)
|
|
2170
|
+
|
|
2171
|
+
def _serve_artifact(self, artifact_path: str) -> None:
|
|
2172
|
+
"""读取并返回指定路径的文件内容(JSON/markdown/text)"""
|
|
2173
|
+
try:
|
|
2174
|
+
p = Path(artifact_path).resolve()
|
|
2175
|
+
# 安全检查: 只允许读取项目 output 目录下的文件
|
|
2176
|
+
output_dir = self.dashboard_state.output_dir.resolve()
|
|
2177
|
+
project_root = self.dashboard_state.base_dir.resolve()
|
|
2178
|
+
if not (p.is_relative_to(output_dir) or p.is_relative_to(project_root / "output")):
|
|
2179
|
+
self._send_json({"error": "Path outside output directory"}, status=HTTPStatus.FORBIDDEN)
|
|
2180
|
+
return
|
|
2181
|
+
if not p.exists():
|
|
2182
|
+
self._send_json({"error": "File not found"}, status=HTTPStatus.NOT_FOUND)
|
|
2183
|
+
return
|
|
2184
|
+
content = p.read_text(encoding="utf-8")
|
|
2185
|
+
suffix = p.suffix.lower()
|
|
2186
|
+
content_type = "application/json" if suffix == ".json" else "text/markdown" if suffix in (".md", ".markdown") else "text/plain"
|
|
2187
|
+
body = content.encode("utf-8")
|
|
2188
|
+
self.send_response(HTTPStatus.OK)
|
|
2189
|
+
self.send_header("Content-Type", f"{content_type}; charset=utf-8")
|
|
2190
|
+
self.send_header("Content-Length", str(len(body)))
|
|
2191
|
+
self.end_headers()
|
|
2192
|
+
self.wfile.write(body)
|
|
2193
|
+
except Exception as exc:
|
|
2194
|
+
self._send_json({"error": str(exc)}, status=HTTPStatus.INTERNAL_SERVER_ERROR)
|
|
2195
|
+
|
|
2196
|
+
def _serve_download(self, file_path: str, inline: bool = False) -> None:
|
|
2197
|
+
"""提供文件下载或内联预览"""
|
|
2198
|
+
try:
|
|
2199
|
+
p = Path(file_path).resolve()
|
|
2200
|
+
output_dir = self.dashboard_state.output_dir.resolve()
|
|
2201
|
+
project_root = self.dashboard_state.base_dir.resolve()
|
|
2202
|
+
if not (p.is_relative_to(output_dir) or p.is_relative_to(project_root / "output")):
|
|
2203
|
+
self._send_json({"error": "Path outside output directory"}, status=HTTPStatus.FORBIDDEN)
|
|
2204
|
+
return
|
|
2205
|
+
if not p.exists():
|
|
2206
|
+
self._send_json({"error": "File not found"}, status=HTTPStatus.NOT_FOUND)
|
|
2207
|
+
return
|
|
2208
|
+
data = p.read_bytes()
|
|
2209
|
+
filename = p.name
|
|
2210
|
+
suffix = p.suffix.lower()
|
|
2211
|
+
content_type = {
|
|
2212
|
+
".json": "application/json",
|
|
2213
|
+
".md": "text/markdown; charset=utf-8",
|
|
2214
|
+
".html": "text/html; charset=utf-8",
|
|
2215
|
+
".csv": "text/csv",
|
|
2216
|
+
".pdf": "application/pdf",
|
|
2217
|
+
".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
2218
|
+
".png": "image/png",
|
|
2219
|
+
".svg": "image/svg+xml",
|
|
2220
|
+
".zip": "application/zip",
|
|
2221
|
+
}.get(suffix, "application/octet-stream")
|
|
2222
|
+
self.send_response(HTTPStatus.OK)
|
|
2223
|
+
self.send_header("Content-Type", content_type)
|
|
2224
|
+
self.send_header("Content-Length", str(len(data)))
|
|
2225
|
+
self.send_header("Content-Disposition", build_content_disposition(filename, inline=inline))
|
|
2226
|
+
self.end_headers()
|
|
2227
|
+
self.wfile.write(data)
|
|
2228
|
+
except Exception as exc:
|
|
2229
|
+
self._send_json({"error": str(exc)}, status=HTTPStatus.INTERNAL_SERVER_ERROR)
|
|
2230
|
+
|
|
2231
|
+
def _preview_summary(self, artifact_path: str) -> None:
|
|
2232
|
+
try:
|
|
2233
|
+
p = Path(artifact_path).resolve()
|
|
2234
|
+
output_dir = self.dashboard_state.output_dir.resolve()
|
|
2235
|
+
if not p.is_relative_to(output_dir):
|
|
2236
|
+
self._send_json({"error": "Path outside output directory"}, status=HTTPStatus.FORBIDDEN)
|
|
2237
|
+
return
|
|
2238
|
+
if not p.exists() or p.suffix.lower() != ".json":
|
|
2239
|
+
self._send_json({"error": "JSON file not found"}, status=HTTPStatus.NOT_FOUND)
|
|
2240
|
+
return
|
|
2241
|
+
payload = json.loads(p.read_text(encoding="utf-8"))
|
|
2242
|
+
rows = summarize_json_preview(p.name, payload)
|
|
2243
|
+
self._send_json({"filename": p.name, "rows": rows, "raw_available": True})
|
|
2244
|
+
except Exception as exc:
|
|
2245
|
+
self._send_json({"error": str(exc)}, status=HTTPStatus.INTERNAL_SERVER_ERROR)
|
|
2246
|
+
|
|
2247
|
+
def _list_artifacts(self) -> None:
|
|
2248
|
+
"""列出研究者关心的精选交付物"""
|
|
2249
|
+
status = self.dashboard_state.load_status()
|
|
2250
|
+
payload = list_researcher_deliverables(
|
|
2251
|
+
self.dashboard_state.output_dir,
|
|
2252
|
+
current_target=status.get("target"),
|
|
2253
|
+
current_disease=status.get("disease"),
|
|
2254
|
+
)
|
|
2255
|
+
payload["current_target"] = status.get("target")
|
|
2256
|
+
payload["current_disease"] = status.get("disease")
|
|
2257
|
+
self._send_json(payload)
|
|
2258
|
+
|
|
2259
|
+
|
|
2260
|
+
def serve_dashboard(host: str = "127.0.0.1", port: int = 18766) -> None:
|
|
2261
|
+
base_dir = Path(__file__).resolve().parents[2]
|
|
2262
|
+
state = DashboardState(base_dir)
|
|
2263
|
+
handler = partial(DashboardHandler, dashboard_state=state)
|
|
2264
|
+
server = ThreadingHTTPServer((host, port), handler)
|
|
2265
|
+
print(f"医学研究工作台 available at http://{host}:{port}")
|
|
2266
|
+
try:
|
|
2267
|
+
server.serve_forever()
|
|
2268
|
+
except KeyboardInterrupt:
|
|
2269
|
+
pass
|
|
2270
|
+
finally:
|
|
2271
|
+
server.server_close()
|
|
2272
|
+
|
|
2273
|
+
|
|
2274
|
+
def main() -> None:
|
|
2275
|
+
parser = argparse.ArgumentParser(description="Local dashboard for target research pipeline")
|
|
2276
|
+
parser.add_argument("--host", default="127.0.0.1")
|
|
2277
|
+
parser.add_argument("--port", type=int, default=18766)
|
|
2278
|
+
args = parser.parse_args()
|
|
2279
|
+
serve_dashboard(args.host, args.port)
|
|
2280
|
+
|
|
2281
|
+
|
|
2282
|
+
if __name__ == "__main__":
|
|
2283
|
+
main()
|