@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.
Files changed (1104) hide show
  1. package/.scale/mcp-servers.yaml +144 -0
  2. package/.scale/skills.json +830 -0
  3. package/.scale/verification.json +52 -0
  4. package/LICENSE +15 -0
  5. package/README.en.md +156 -0
  6. package/README.md +156 -0
  7. package/dist/adapters/AiderAdapter.d.ts +22 -0
  8. package/dist/adapters/AiderAdapter.js +262 -0
  9. package/dist/adapters/AiderAdapter.js.map +1 -0
  10. package/dist/adapters/AntigravityAdapter.d.ts +4 -0
  11. package/dist/adapters/AntigravityAdapter.js +21 -0
  12. package/dist/adapters/AntigravityAdapter.js.map +1 -0
  13. package/dist/adapters/ClaudeCodeAdapter.d.ts +54 -0
  14. package/dist/adapters/ClaudeCodeAdapter.js +185 -0
  15. package/dist/adapters/ClaudeCodeAdapter.js.map +1 -0
  16. package/dist/adapters/ClineAdapter.d.ts +4 -0
  17. package/dist/adapters/ClineAdapter.js +20 -0
  18. package/dist/adapters/ClineAdapter.js.map +1 -0
  19. package/dist/adapters/CodexAdapter.d.ts +15 -0
  20. package/dist/adapters/CodexAdapter.js +160 -0
  21. package/dist/adapters/CodexAdapter.js.map +1 -0
  22. package/dist/adapters/CursorAdapter.d.ts +14 -0
  23. package/dist/adapters/CursorAdapter.js +171 -0
  24. package/dist/adapters/CursorAdapter.js.map +1 -0
  25. package/dist/adapters/DeepSeekTuiAdapter.d.ts +19 -0
  26. package/dist/adapters/DeepSeekTuiAdapter.js +263 -0
  27. package/dist/adapters/DeepSeekTuiAdapter.js.map +1 -0
  28. package/dist/adapters/DoubaoAdapter.d.ts +14 -0
  29. package/dist/adapters/DoubaoAdapter.js +184 -0
  30. package/dist/adapters/DoubaoAdapter.js.map +1 -0
  31. package/dist/adapters/GeminiAdapter.d.ts +14 -0
  32. package/dist/adapters/GeminiAdapter.js +163 -0
  33. package/dist/adapters/GeminiAdapter.js.map +1 -0
  34. package/dist/adapters/GenericProjectAgentAdapter.d.ts +29 -0
  35. package/dist/adapters/GenericProjectAgentAdapter.js +204 -0
  36. package/dist/adapters/GenericProjectAgentAdapter.js.map +1 -0
  37. package/dist/adapters/HermesAdapter.d.ts +14 -0
  38. package/dist/adapters/HermesAdapter.js +163 -0
  39. package/dist/adapters/HermesAdapter.js.map +1 -0
  40. package/dist/adapters/JCodeAdapter.d.ts +4 -0
  41. package/dist/adapters/JCodeAdapter.js +19 -0
  42. package/dist/adapters/JCodeAdapter.js.map +1 -0
  43. package/dist/adapters/KiloCodeAdapter.d.ts +4 -0
  44. package/dist/adapters/KiloCodeAdapter.js +20 -0
  45. package/dist/adapters/KiloCodeAdapter.js.map +1 -0
  46. package/dist/adapters/KimiAdapter.d.ts +14 -0
  47. package/dist/adapters/KimiAdapter.js +183 -0
  48. package/dist/adapters/KimiAdapter.js.map +1 -0
  49. package/dist/adapters/KiroAdapter.d.ts +14 -0
  50. package/dist/adapters/KiroAdapter.js +180 -0
  51. package/dist/adapters/KiroAdapter.js.map +1 -0
  52. package/dist/adapters/OpenClawAdapter.d.ts +14 -0
  53. package/dist/adapters/OpenClawAdapter.js +163 -0
  54. package/dist/adapters/OpenClawAdapter.js.map +1 -0
  55. package/dist/adapters/OpenCodeAdapter.d.ts +14 -0
  56. package/dist/adapters/OpenCodeAdapter.js +172 -0
  57. package/dist/adapters/OpenCodeAdapter.js.map +1 -0
  58. package/dist/adapters/QCoderAdapter.d.ts +14 -0
  59. package/dist/adapters/QCoderAdapter.js +159 -0
  60. package/dist/adapters/QCoderAdapter.js.map +1 -0
  61. package/dist/adapters/QoderAdapter.d.ts +4 -0
  62. package/dist/adapters/QoderAdapter.js +21 -0
  63. package/dist/adapters/QoderAdapter.js.map +1 -0
  64. package/dist/adapters/TraeAdapter.d.ts +14 -0
  65. package/dist/adapters/TraeAdapter.js +159 -0
  66. package/dist/adapters/TraeAdapter.js.map +1 -0
  67. package/dist/adapters/VSCAdapter.d.ts +14 -0
  68. package/dist/adapters/VSCAdapter.js +159 -0
  69. package/dist/adapters/VSCAdapter.js.map +1 -0
  70. package/dist/adapters/WindsurfAdapter.d.ts +14 -0
  71. package/dist/adapters/WindsurfAdapter.js +185 -0
  72. package/dist/adapters/WindsurfAdapter.js.map +1 -0
  73. package/dist/adapters/WorkBuddyAdapter.d.ts +14 -0
  74. package/dist/adapters/WorkBuddyAdapter.js +159 -0
  75. package/dist/adapters/WorkBuddyAdapter.js.map +1 -0
  76. package/dist/adapters/index.d.ts +32 -0
  77. package/dist/adapters/index.js +87 -0
  78. package/dist/adapters/index.js.map +1 -0
  79. package/dist/agents/AgentChannel.d.ts +43 -0
  80. package/dist/agents/AgentChannel.js +136 -0
  81. package/dist/agents/AgentChannel.js.map +1 -0
  82. package/dist/agents/AgentCoordinator.d.ts +29 -0
  83. package/dist/agents/AgentCoordinator.js +136 -0
  84. package/dist/agents/AgentCoordinator.js.map +1 -0
  85. package/dist/agents/AgentDispatcher.d.ts +24 -0
  86. package/dist/agents/AgentDispatcher.js +112 -0
  87. package/dist/agents/AgentDispatcher.js.map +1 -0
  88. package/dist/agents/AgentManager.d.ts +14 -0
  89. package/dist/agents/AgentManager.js +85 -0
  90. package/dist/agents/AgentManager.js.map +1 -0
  91. package/dist/agents/AgentPool.d.ts +59 -0
  92. package/dist/agents/AgentPool.js +192 -0
  93. package/dist/agents/AgentPool.js.map +1 -0
  94. package/dist/agents/AgentRegistry.d.ts +20 -0
  95. package/dist/agents/AgentRegistry.js +36 -0
  96. package/dist/agents/AgentRegistry.js.map +1 -0
  97. package/dist/agents/AgentSourceLoader.d.ts +73 -0
  98. package/dist/agents/AgentSourceLoader.js +103 -0
  99. package/dist/agents/AgentSourceLoader.js.map +1 -0
  100. package/dist/agents/IAgent.d.ts +53 -0
  101. package/dist/agents/IAgent.js +4 -0
  102. package/dist/agents/IAgent.js.map +1 -0
  103. package/dist/agents/LeadershipPresets.d.ts +16 -0
  104. package/dist/agents/LeadershipPresets.js +152 -0
  105. package/dist/agents/LeadershipPresets.js.map +1 -0
  106. package/dist/agents/definitions/debugger.d.ts +2 -0
  107. package/dist/agents/definitions/debugger.js +6 -0
  108. package/dist/agents/definitions/debugger.js.map +1 -0
  109. package/dist/agents/definitions/doc-writer.d.ts +2 -0
  110. package/dist/agents/definitions/doc-writer.js +6 -0
  111. package/dist/agents/definitions/doc-writer.js.map +1 -0
  112. package/dist/agents/definitions/implementer.d.ts +2 -0
  113. package/dist/agents/definitions/implementer.js +6 -0
  114. package/dist/agents/definitions/implementer.js.map +1 -0
  115. package/dist/agents/definitions/planner.d.ts +2 -0
  116. package/dist/agents/definitions/planner.js +6 -0
  117. package/dist/agents/definitions/planner.js.map +1 -0
  118. package/dist/agents/definitions/researcher.d.ts +2 -0
  119. package/dist/agents/definitions/researcher.js +6 -0
  120. package/dist/agents/definitions/researcher.js.map +1 -0
  121. package/dist/agents/definitions/reviewer.d.ts +2 -0
  122. package/dist/agents/definitions/reviewer.js +6 -0
  123. package/dist/agents/definitions/reviewer.js.map +1 -0
  124. package/dist/agents/definitions/security.d.ts +2 -0
  125. package/dist/agents/definitions/security.js +6 -0
  126. package/dist/agents/definitions/security.js.map +1 -0
  127. package/dist/agents/definitions/tester.d.ts +2 -0
  128. package/dist/agents/definitions/tester.js +6 -0
  129. package/dist/agents/definitions/tester.js.map +1 -0
  130. package/dist/agents/index.d.ts +23 -0
  131. package/dist/agents/index.js +44 -0
  132. package/dist/agents/index.js.map +1 -0
  133. package/dist/agents/profiles.d.ts +26 -0
  134. package/dist/agents/profiles.js +197 -0
  135. package/dist/agents/profiles.js.map +1 -0
  136. package/dist/agents/types.d.ts +262 -0
  137. package/dist/agents/types.js +4 -0
  138. package/dist/agents/types.js.map +1 -0
  139. package/dist/api/cli.d.ts +2 -0
  140. package/dist/api/cli.js +6678 -0
  141. package/dist/api/cli.js.map +1 -0
  142. package/dist/api/doctor.d.ts +83 -0
  143. package/dist/api/doctor.js +982 -0
  144. package/dist/api/doctor.js.map +1 -0
  145. package/dist/api/mcp.d.ts +32 -0
  146. package/dist/api/mcp.js +223 -0
  147. package/dist/api/mcp.js.map +1 -0
  148. package/dist/api/medscale.d.ts +2 -0
  149. package/dist/api/medscale.js +20 -0
  150. package/dist/api/medscale.js.map +1 -0
  151. package/dist/api/quickstart.d.ts +86 -0
  152. package/dist/api/quickstart.js +291 -0
  153. package/dist/api/quickstart.js.map +1 -0
  154. package/dist/artifact/fsm.d.ts +41 -0
  155. package/dist/artifact/fsm.js +221 -0
  156. package/dist/artifact/fsm.js.map +1 -0
  157. package/dist/artifact/fsmDefinitions.d.ts +18 -0
  158. package/dist/artifact/fsmDefinitions.js +296 -0
  159. package/dist/artifact/fsmDefinitions.js.map +1 -0
  160. package/dist/artifact/sqliteStore.d.ts +61 -0
  161. package/dist/artifact/sqliteStore.js +381 -0
  162. package/dist/artifact/sqliteStore.js.map +1 -0
  163. package/dist/artifact/store.d.ts +49 -0
  164. package/dist/artifact/store.js +116 -0
  165. package/dist/artifact/store.js.map +1 -0
  166. package/dist/artifact/types.d.ts +535 -0
  167. package/dist/artifact/types.js +74 -0
  168. package/dist/artifact/types.js.map +1 -0
  169. package/dist/bootstrap/DependencyBootstrap.d.ts +112 -0
  170. package/dist/bootstrap/DependencyBootstrap.js +1046 -0
  171. package/dist/bootstrap/DependencyBootstrap.js.map +1 -0
  172. package/dist/bootstrap/DependencyBootstrapRenderer.d.ts +3 -0
  173. package/dist/bootstrap/DependencyBootstrapRenderer.js +138 -0
  174. package/dist/bootstrap/DependencyBootstrapRenderer.js.map +1 -0
  175. package/dist/bridge/PythonBridge.d.ts +80 -0
  176. package/dist/bridge/PythonBridge.js +437 -0
  177. package/dist/bridge/PythonBridge.js.map +1 -0
  178. package/dist/bridge/index.d.ts +2 -0
  179. package/dist/bridge/index.js +7 -0
  180. package/dist/bridge/index.js.map +1 -0
  181. package/dist/bridge/medicalWorkflows.d.ts +29 -0
  182. package/dist/bridge/medicalWorkflows.js +156 -0
  183. package/dist/bridge/medicalWorkflows.js.map +1 -0
  184. package/dist/bridge/types.d.ts +381 -0
  185. package/dist/bridge/types.js +113 -0
  186. package/dist/bridge/types.js.map +1 -0
  187. package/dist/cache/ScanCache.d.ts +41 -0
  188. package/dist/cache/ScanCache.js +120 -0
  189. package/dist/cache/ScanCache.js.map +1 -0
  190. package/dist/capabilities/BrowserCapability.d.ts +30 -0
  191. package/dist/capabilities/BrowserCapability.js +73 -0
  192. package/dist/capabilities/BrowserCapability.js.map +1 -0
  193. package/dist/capabilities/BrowserQACapability.d.ts +165 -0
  194. package/dist/capabilities/BrowserQACapability.js +438 -0
  195. package/dist/capabilities/BrowserQACapability.js.map +1 -0
  196. package/dist/capabilities/CapabilityRegistry.d.ts +17 -0
  197. package/dist/capabilities/CapabilityRegistry.js +65 -0
  198. package/dist/capabilities/CapabilityRegistry.js.map +1 -0
  199. package/dist/capabilities/ComputerCapability.d.ts +28 -0
  200. package/dist/capabilities/ComputerCapability.js +40 -0
  201. package/dist/capabilities/ComputerCapability.js.map +1 -0
  202. package/dist/capabilities/InstalledSkillsIntegration.d.ts +69 -0
  203. package/dist/capabilities/InstalledSkillsIntegration.js +240 -0
  204. package/dist/capabilities/InstalledSkillsIntegration.js.map +1 -0
  205. package/dist/capabilities/SearchCapability.d.ts +46 -0
  206. package/dist/capabilities/SearchCapability.js +88 -0
  207. package/dist/capabilities/SearchCapability.js.map +1 -0
  208. package/dist/capabilities/index.d.ts +6 -0
  209. package/dist/capabilities/index.js +9 -0
  210. package/dist/capabilities/index.js.map +1 -0
  211. package/dist/capabilities/types.d.ts +92 -0
  212. package/dist/capabilities/types.js +7 -0
  213. package/dist/capabilities/types.js.map +1 -0
  214. package/dist/cli/autofixCommands.d.ts +22 -0
  215. package/dist/cli/autofixCommands.js +32 -0
  216. package/dist/cli/autofixCommands.js.map +1 -0
  217. package/dist/cli/cortexCommands.d.ts +71 -0
  218. package/dist/cli/cortexCommands.js +335 -0
  219. package/dist/cli/cortexCommands.js.map +1 -0
  220. package/dist/cli/costCommands.d.ts +13 -0
  221. package/dist/cli/costCommands.js +48 -0
  222. package/dist/cli/costCommands.js.map +1 -0
  223. package/dist/cli/evolutionCommands.d.ts +112 -0
  224. package/dist/cli/evolutionCommands.js +246 -0
  225. package/dist/cli/evolutionCommands.js.map +1 -0
  226. package/dist/cli/gateStatusCommands.d.ts +1 -0
  227. package/dist/cli/gateStatusCommands.js +52 -0
  228. package/dist/cli/gateStatusCommands.js.map +1 -0
  229. package/dist/cli/liteCommands.d.ts +81 -0
  230. package/dist/cli/liteCommands.js +148 -0
  231. package/dist/cli/liteCommands.js.map +1 -0
  232. package/dist/cli/orchCommands.d.ts +43 -0
  233. package/dist/cli/orchCommands.js +135 -0
  234. package/dist/cli/orchCommands.js.map +1 -0
  235. package/dist/cli/phaseCommands.d.ts +248 -0
  236. package/dist/cli/phaseCommands.js +1878 -0
  237. package/dist/cli/phaseCommands.js.map +1 -0
  238. package/dist/cli/promptCommands.d.ts +1 -0
  239. package/dist/cli/promptCommands.js +57 -0
  240. package/dist/cli/promptCommands.js.map +1 -0
  241. package/dist/cli/qaCommands.d.ts +22 -0
  242. package/dist/cli/qaCommands.js +84 -0
  243. package/dist/cli/qaCommands.js.map +1 -0
  244. package/dist/cli/quickstartCommands.d.ts +17 -0
  245. package/dist/cli/quickstartCommands.js +47 -0
  246. package/dist/cli/quickstartCommands.js.map +1 -0
  247. package/dist/cli/runCommand.d.ts +39 -0
  248. package/dist/cli/runCommand.js +113 -0
  249. package/dist/cli/runCommand.js.map +1 -0
  250. package/dist/cli/scoreCommands.d.ts +1 -0
  251. package/dist/cli/scoreCommands.js +112 -0
  252. package/dist/cli/scoreCommands.js.map +1 -0
  253. package/dist/cli/shieldCommands.d.ts +30 -0
  254. package/dist/cli/shieldCommands.js +212 -0
  255. package/dist/cli/shieldCommands.js.map +1 -0
  256. package/dist/cli/targetCommands.d.ts +552 -0
  257. package/dist/cli/targetCommands.js +3173 -0
  258. package/dist/cli/targetCommands.js.map +1 -0
  259. package/dist/cli/tuiCommands.d.ts +7 -0
  260. package/dist/cli/tuiCommands.js +33 -0
  261. package/dist/cli/tuiCommands.js.map +1 -0
  262. package/dist/cli/vibeCommands.d.ts +64 -0
  263. package/dist/cli/vibeCommands.js +221 -0
  264. package/dist/cli/vibeCommands.js.map +1 -0
  265. package/dist/codegraph/CodeIntelligence.d.ts +147 -0
  266. package/dist/codegraph/CodeIntelligence.js +681 -0
  267. package/dist/codegraph/CodeIntelligence.js.map +1 -0
  268. package/dist/config/profiles.d.ts +64 -0
  269. package/dist/config/profiles.js +223 -0
  270. package/dist/config/profiles.js.map +1 -0
  271. package/dist/context/AntiPatternRegistry.d.ts +38 -0
  272. package/dist/context/AntiPatternRegistry.js +203 -0
  273. package/dist/context/AntiPatternRegistry.js.map +1 -0
  274. package/dist/context/CavemanCompressor.d.ts +20 -0
  275. package/dist/context/CavemanCompressor.js +14 -0
  276. package/dist/context/CavemanCompressor.js.map +1 -0
  277. package/dist/context/ContextBudget.d.ts +128 -0
  278. package/dist/context/ContextBudget.js +423 -0
  279. package/dist/context/ContextBudget.js.map +1 -0
  280. package/dist/context/ContextBuilder.d.ts +71 -0
  281. package/dist/context/ContextBuilder.js +372 -0
  282. package/dist/context/ContextBuilder.js.map +1 -0
  283. package/dist/context/ContextCompiler.d.ts +34 -0
  284. package/dist/context/ContextCompiler.js +120 -0
  285. package/dist/context/ContextCompiler.js.map +1 -0
  286. package/dist/context/ProjectAnatomy.d.ts +18 -0
  287. package/dist/context/ProjectAnatomy.js +287 -0
  288. package/dist/context/ProjectAnatomy.js.map +1 -0
  289. package/dist/context/SessionStartSequence.d.ts +54 -0
  290. package/dist/context/SessionStartSequence.js +162 -0
  291. package/dist/context/SessionStartSequence.js.map +1 -0
  292. package/dist/core/ExternalCommand.d.ts +9 -0
  293. package/dist/core/ExternalCommand.js +70 -0
  294. package/dist/core/ExternalCommand.js.map +1 -0
  295. package/dist/core/GbrainRuntime.d.ts +25 -0
  296. package/dist/core/GbrainRuntime.js +270 -0
  297. package/dist/core/GbrainRuntime.js.map +1 -0
  298. package/dist/core/container.d.ts +14 -0
  299. package/dist/core/container.js +35 -0
  300. package/dist/core/container.js.map +1 -0
  301. package/dist/core/eventBus.d.ts +60 -0
  302. package/dist/core/eventBus.js +157 -0
  303. package/dist/core/eventBus.js.map +1 -0
  304. package/dist/core/logger.d.ts +5 -0
  305. package/dist/core/logger.js +51 -0
  306. package/dist/core/logger.js.map +1 -0
  307. package/dist/cortex/GovernanceMetrics.d.ts +66 -0
  308. package/dist/cortex/GovernanceMetrics.js +230 -0
  309. package/dist/cortex/GovernanceMetrics.js.map +1 -0
  310. package/dist/cortex/InstinctExtractor.d.ts +61 -0
  311. package/dist/cortex/InstinctExtractor.js +184 -0
  312. package/dist/cortex/InstinctExtractor.js.map +1 -0
  313. package/dist/cortex/InstinctStore.d.ts +54 -0
  314. package/dist/cortex/InstinctStore.js +266 -0
  315. package/dist/cortex/InstinctStore.js.map +1 -0
  316. package/dist/cortex/ReflexionEngine.d.ts +34 -0
  317. package/dist/cortex/ReflexionEngine.js +157 -0
  318. package/dist/cortex/ReflexionEngine.js.map +1 -0
  319. package/dist/cortex/SessionInjector.d.ts +44 -0
  320. package/dist/cortex/SessionInjector.js +127 -0
  321. package/dist/cortex/SessionInjector.js.map +1 -0
  322. package/dist/cortex/adapters/ClaudeAdapter.d.ts +17 -0
  323. package/dist/cortex/adapters/ClaudeAdapter.js +61 -0
  324. package/dist/cortex/adapters/ClaudeAdapter.js.map +1 -0
  325. package/dist/cortex/adapters/CodexAdapter.d.ts +10 -0
  326. package/dist/cortex/adapters/CodexAdapter.js +52 -0
  327. package/dist/cortex/adapters/CodexAdapter.js.map +1 -0
  328. package/dist/cortex/adapters/CursorAdapter.d.ts +10 -0
  329. package/dist/cortex/adapters/CursorAdapter.js +46 -0
  330. package/dist/cortex/adapters/CursorAdapter.js.map +1 -0
  331. package/dist/cortex/adapters/GeminiAdapter.d.ts +11 -0
  332. package/dist/cortex/adapters/GeminiAdapter.js +48 -0
  333. package/dist/cortex/adapters/GeminiAdapter.js.map +1 -0
  334. package/dist/dashboard/DashboardServer.d.ts +86 -0
  335. package/dist/dashboard/DashboardServer.js +380 -0
  336. package/dist/dashboard/DashboardServer.js.map +1 -0
  337. package/dist/dashboard/MedicalWorkflowData.d.ts +155 -0
  338. package/dist/dashboard/MedicalWorkflowData.js +664 -0
  339. package/dist/dashboard/MedicalWorkflowData.js.map +1 -0
  340. package/dist/dashboard/MetricsAggregator.d.ts +38 -0
  341. package/dist/dashboard/MetricsAggregator.js +99 -0
  342. package/dist/dashboard/MetricsAggregator.js.map +1 -0
  343. package/dist/dashboard/index.d.ts +4 -0
  344. package/dist/dashboard/index.js +3 -0
  345. package/dist/dashboard/index.js.map +1 -0
  346. package/dist/dashboard/server.d.ts +52 -0
  347. package/dist/dashboard/server.js +84 -0
  348. package/dist/dashboard/server.js.map +1 -0
  349. package/dist/env/EnvironmentDoctor.d.ts +66 -0
  350. package/dist/env/EnvironmentDoctor.js +581 -0
  351. package/dist/env/EnvironmentDoctor.js.map +1 -0
  352. package/dist/eval/BenchmarkPublisher.d.ts +25 -0
  353. package/dist/eval/BenchmarkPublisher.js +27 -0
  354. package/dist/eval/BenchmarkPublisher.js.map +1 -0
  355. package/dist/eval/WorkflowEval.d.ts +161 -0
  356. package/dist/eval/WorkflowEval.js +377 -0
  357. package/dist/eval/WorkflowEval.js.map +1 -0
  358. package/dist/evolution/AutoDefectCreator.d.ts +43 -0
  359. package/dist/evolution/AutoDefectCreator.js +157 -0
  360. package/dist/evolution/AutoDefectCreator.js.map +1 -0
  361. package/dist/evolution/BehaviorTracker.d.ts +46 -0
  362. package/dist/evolution/BehaviorTracker.js +67 -0
  363. package/dist/evolution/BehaviorTracker.js.map +1 -0
  364. package/dist/evolution/EvolutionEngine.d.ts +102 -0
  365. package/dist/evolution/EvolutionEngine.js +326 -0
  366. package/dist/evolution/EvolutionEngine.js.map +1 -0
  367. package/dist/evolution/EvolutionEvaluator.d.ts +61 -0
  368. package/dist/evolution/EvolutionEvaluator.js +118 -0
  369. package/dist/evolution/EvolutionEvaluator.js.map +1 -0
  370. package/dist/evolution/LessonValidator.d.ts +36 -0
  371. package/dist/evolution/LessonValidator.js +132 -0
  372. package/dist/evolution/LessonValidator.js.map +1 -0
  373. package/dist/evolution/PatternExtractor.d.ts +40 -0
  374. package/dist/evolution/PatternExtractor.js +83 -0
  375. package/dist/evolution/PatternExtractor.js.map +1 -0
  376. package/dist/evolution/RuleMaturity.d.ts +39 -0
  377. package/dist/evolution/RuleMaturity.js +70 -0
  378. package/dist/evolution/RuleMaturity.js.map +1 -0
  379. package/dist/evolution/SessionLearnings.d.ts +70 -0
  380. package/dist/evolution/SessionLearnings.js +217 -0
  381. package/dist/evolution/SessionLearnings.js.map +1 -0
  382. package/dist/evolution/SkillCreator.d.ts +75 -0
  383. package/dist/evolution/SkillCreator.js +219 -0
  384. package/dist/evolution/SkillCreator.js.map +1 -0
  385. package/dist/fsm/FSMAgentBridge.d.ts +59 -0
  386. package/dist/fsm/FSMAgentBridge.js +193 -0
  387. package/dist/fsm/FSMAgentBridge.js.map +1 -0
  388. package/dist/fsm/index.d.ts +2 -0
  389. package/dist/fsm/index.js +3 -0
  390. package/dist/fsm/index.js.map +1 -0
  391. package/dist/governance/GovernanceRoi.d.ts +30 -0
  392. package/dist/governance/GovernanceRoi.js +102 -0
  393. package/dist/governance/GovernanceRoi.js.map +1 -0
  394. package/dist/governance/ProgressiveGovernance.d.ts +22 -0
  395. package/dist/governance/ProgressiveGovernance.js +159 -0
  396. package/dist/governance/ProgressiveGovernance.js.map +1 -0
  397. package/dist/guardrails/ActiveRedTeam.d.ts +46 -0
  398. package/dist/guardrails/ActiveRedTeam.js +203 -0
  399. package/dist/guardrails/ActiveRedTeam.js.map +1 -0
  400. package/dist/guardrails/DependencyAuditor.d.ts +68 -0
  401. package/dist/guardrails/DependencyAuditor.js +378 -0
  402. package/dist/guardrails/DependencyAuditor.js.map +1 -0
  403. package/dist/guardrails/DetectorEnhanced.d.ts +111 -0
  404. package/dist/guardrails/DetectorEnhanced.js +202 -0
  405. package/dist/guardrails/DetectorEnhanced.js.map +1 -0
  406. package/dist/guardrails/GateEvaluator.d.ts +18 -0
  407. package/dist/guardrails/GateEvaluator.js +129 -0
  408. package/dist/guardrails/GateEvaluator.js.map +1 -0
  409. package/dist/guardrails/Gateway.d.ts +26 -0
  410. package/dist/guardrails/Gateway.js +56 -0
  411. package/dist/guardrails/Gateway.js.map +1 -0
  412. package/dist/guardrails/OWASPDetector.d.ts +58 -0
  413. package/dist/guardrails/OWASPDetector.js +508 -0
  414. package/dist/guardrails/OWASPDetector.js.map +1 -0
  415. package/dist/guardrails/ReviewEnforcer.d.ts +52 -0
  416. package/dist/guardrails/ReviewEnforcer.js +117 -0
  417. package/dist/guardrails/ReviewEnforcer.js.map +1 -0
  418. package/dist/guardrails/advancedDetectors.d.ts +38 -0
  419. package/dist/guardrails/advancedDetectors.js +188 -0
  420. package/dist/guardrails/advancedDetectors.js.map +1 -0
  421. package/dist/guardrails/detectors.d.ts +34 -0
  422. package/dist/guardrails/detectors.js +332 -0
  423. package/dist/guardrails/detectors.js.map +1 -0
  424. package/dist/guardrails/roles.d.ts +4 -0
  425. package/dist/guardrails/roles.js +54 -0
  426. package/dist/guardrails/roles.js.map +1 -0
  427. package/dist/hooks/BugPatternDetector.d.ts +36 -0
  428. package/dist/hooks/BugPatternDetector.js +207 -0
  429. package/dist/hooks/BugPatternDetector.js.map +1 -0
  430. package/dist/hooks/HookDeployer.d.ts +44 -0
  431. package/dist/hooks/HookDeployer.js +144 -0
  432. package/dist/hooks/HookDeployer.js.map +1 -0
  433. package/dist/hooks/HookGeneratorEnhanced.d.ts +67 -0
  434. package/dist/hooks/HookGeneratorEnhanced.js +641 -0
  435. package/dist/hooks/HookGeneratorEnhanced.js.map +1 -0
  436. package/dist/hooks/WorkflowHooksManager.d.ts +30 -0
  437. package/dist/hooks/WorkflowHooksManager.js +160 -0
  438. package/dist/hooks/WorkflowHooksManager.js.map +1 -0
  439. package/dist/hooks/index.d.ts +6 -0
  440. package/dist/hooks/index.js +5 -0
  441. package/dist/hooks/index.js.map +1 -0
  442. package/dist/i18n/Language.d.ts +9 -0
  443. package/dist/i18n/Language.js +38 -0
  444. package/dist/i18n/Language.js.map +1 -0
  445. package/dist/index.d.ts +101 -0
  446. package/dist/index.js +104 -0
  447. package/dist/index.js.map +1 -0
  448. package/dist/knowledge/CerebrumManager.d.ts +25 -0
  449. package/dist/knowledge/CerebrumManager.js +127 -0
  450. package/dist/knowledge/CerebrumManager.js.map +1 -0
  451. package/dist/knowledge/GraphifyKnowledgeBase.d.ts +38 -0
  452. package/dist/knowledge/GraphifyKnowledgeBase.js +409 -0
  453. package/dist/knowledge/GraphifyKnowledgeBase.js.map +1 -0
  454. package/dist/knowledge/KnowledgeBase.d.ts +51 -0
  455. package/dist/knowledge/KnowledgeBase.js +182 -0
  456. package/dist/knowledge/KnowledgeBase.js.map +1 -0
  457. package/dist/knowledge/SQLiteKnowledgeBase.d.ts +29 -0
  458. package/dist/knowledge/SQLiteKnowledgeBase.js +203 -0
  459. package/dist/knowledge/SQLiteKnowledgeBase.js.map +1 -0
  460. package/dist/knowledge/TfidfIndex.d.ts +50 -0
  461. package/dist/knowledge/TfidfIndex.js +177 -0
  462. package/dist/knowledge/TfidfIndex.js.map +1 -0
  463. package/dist/knowledge/UbiquitousLanguageManager.d.ts +49 -0
  464. package/dist/knowledge/UbiquitousLanguageManager.js +133 -0
  465. package/dist/knowledge/UbiquitousLanguageManager.js.map +1 -0
  466. package/dist/memory/MemoryBrain.d.ts +146 -0
  467. package/dist/memory/MemoryBrain.js +679 -0
  468. package/dist/memory/MemoryBrain.js.map +1 -0
  469. package/dist/memory/MemoryFabric.d.ts +130 -0
  470. package/dist/memory/MemoryFabric.js +317 -0
  471. package/dist/memory/MemoryFabric.js.map +1 -0
  472. package/dist/memory/MemoryIntelligence.d.ts +42 -0
  473. package/dist/memory/MemoryIntelligence.js +215 -0
  474. package/dist/memory/MemoryIntelligence.js.map +1 -0
  475. package/dist/memory/MemoryLearning.d.ts +62 -0
  476. package/dist/memory/MemoryLearning.js +209 -0
  477. package/dist/memory/MemoryLearning.js.map +1 -0
  478. package/dist/memory/MemoryProviders.d.ts +165 -0
  479. package/dist/memory/MemoryProviders.js +940 -0
  480. package/dist/memory/MemoryProviders.js.map +1 -0
  481. package/dist/memory/MemoryReview.d.ts +65 -0
  482. package/dist/memory/MemoryReview.js +260 -0
  483. package/dist/memory/MemoryReview.js.map +1 -0
  484. package/dist/memory/index.d.ts +6 -0
  485. package/dist/memory/index.js +7 -0
  486. package/dist/memory/index.js.map +1 -0
  487. package/dist/orchestration/EffectsWiring.d.ts +8 -0
  488. package/dist/orchestration/EffectsWiring.js +87 -0
  489. package/dist/orchestration/EffectsWiring.js.map +1 -0
  490. package/dist/orchestrator/OrchestratorDaemon.d.ts +44 -0
  491. package/dist/orchestrator/OrchestratorDaemon.js +150 -0
  492. package/dist/orchestrator/OrchestratorDaemon.js.map +1 -0
  493. package/dist/orchestrator/PolicyLoader.d.ts +80 -0
  494. package/dist/orchestrator/PolicyLoader.js +229 -0
  495. package/dist/orchestrator/PolicyLoader.js.map +1 -0
  496. package/dist/orchestrator/ReconciliationLoop.d.ts +71 -0
  497. package/dist/orchestrator/ReconciliationLoop.js +266 -0
  498. package/dist/orchestrator/ReconciliationLoop.js.map +1 -0
  499. package/dist/orchestrator/TrackerAdapter.d.ts +60 -0
  500. package/dist/orchestrator/TrackerAdapter.js +147 -0
  501. package/dist/orchestrator/TrackerAdapter.js.map +1 -0
  502. package/dist/orchestrator/WorkspaceManager.d.ts +66 -0
  503. package/dist/orchestrator/WorkspaceManager.js +257 -0
  504. package/dist/orchestrator/WorkspaceManager.js.map +1 -0
  505. package/dist/output/BrandThemeLoader.d.ts +54 -0
  506. package/dist/output/BrandThemeLoader.js +340 -0
  507. package/dist/output/BrandThemeLoader.js.map +1 -0
  508. package/dist/output/GovernanceDashboard.d.ts +59 -0
  509. package/dist/output/GovernanceDashboard.js +281 -0
  510. package/dist/output/GovernanceDashboard.js.map +1 -0
  511. package/dist/output/HTMLArtifactLayer.d.ts +97 -0
  512. package/dist/output/HTMLArtifactLayer.js +576 -0
  513. package/dist/output/HTMLArtifactLayer.js.map +1 -0
  514. package/dist/output/HTMLDocumentRenderer.d.ts +83 -0
  515. package/dist/output/HTMLDocumentRenderer.js +718 -0
  516. package/dist/output/HTMLDocumentRenderer.js.map +1 -0
  517. package/dist/output/UIPrototypeRenderer.d.ts +61 -0
  518. package/dist/output/UIPrototypeRenderer.js +500 -0
  519. package/dist/output/UIPrototypeRenderer.js.map +1 -0
  520. package/dist/output/index.d.ts +10 -0
  521. package/dist/output/index.js +8 -0
  522. package/dist/output/index.js.map +1 -0
  523. package/dist/prompts/PhasePromptRegistry.d.ts +53 -0
  524. package/dist/prompts/PhasePromptRegistry.js +517 -0
  525. package/dist/prompts/PhasePromptRegistry.js.map +1 -0
  526. package/dist/prompts/PromptOptimizer.d.ts +42 -0
  527. package/dist/prompts/PromptOptimizer.js +309 -0
  528. package/dist/prompts/PromptOptimizer.js.map +1 -0
  529. package/dist/prompts/VibeTemplateGallery.d.ts +25 -0
  530. package/dist/prompts/VibeTemplateGallery.js +295 -0
  531. package/dist/prompts/VibeTemplateGallery.js.map +1 -0
  532. package/dist/qa/BrowserDaemon.d.ts +23 -0
  533. package/dist/qa/BrowserDaemon.js +79 -0
  534. package/dist/qa/BrowserDaemon.js.map +1 -0
  535. package/dist/qa/E2ETestOrchestrator.d.ts +14 -0
  536. package/dist/qa/E2ETestOrchestrator.js +19 -0
  537. package/dist/qa/E2ETestOrchestrator.js.map +1 -0
  538. package/dist/review/CrossModelReviewer.d.ts +35 -0
  539. package/dist/review/CrossModelReviewer.js +75 -0
  540. package/dist/review/CrossModelReviewer.js.map +1 -0
  541. package/dist/review/ReviewAggregator.d.ts +13 -0
  542. package/dist/review/ReviewAggregator.js +28 -0
  543. package/dist/review/ReviewAggregator.js.map +1 -0
  544. package/dist/review/reviewCommands.d.ts +15 -0
  545. package/dist/review/reviewCommands.js +24 -0
  546. package/dist/review/reviewCommands.js.map +1 -0
  547. package/dist/routing/LocalModelProvider.d.ts +11 -0
  548. package/dist/routing/LocalModelProvider.js +21 -0
  549. package/dist/routing/LocalModelProvider.js.map +1 -0
  550. package/dist/routing/ModelRouter.d.ts +42 -0
  551. package/dist/routing/ModelRouter.js +94 -0
  552. package/dist/routing/ModelRouter.js.map +1 -0
  553. package/dist/routing/PromptCachePolicy.d.ts +37 -0
  554. package/dist/routing/PromptCachePolicy.js +97 -0
  555. package/dist/routing/PromptCachePolicy.js.map +1 -0
  556. package/dist/runtime/AiOsRuntime.d.ts +485 -0
  557. package/dist/runtime/AiOsRuntime.js +1846 -0
  558. package/dist/runtime/AiOsRuntime.js.map +1 -0
  559. package/dist/runtime/CostAnalyzer.d.ts +53 -0
  560. package/dist/runtime/CostAnalyzer.js +160 -0
  561. package/dist/runtime/CostAnalyzer.js.map +1 -0
  562. package/dist/runtime/CostOptimizer.d.ts +11 -0
  563. package/dist/runtime/CostOptimizer.js +21 -0
  564. package/dist/runtime/CostOptimizer.js.map +1 -0
  565. package/dist/runtime/ExecutionLedger.d.ts +46 -0
  566. package/dist/runtime/ExecutionLedger.js +71 -0
  567. package/dist/runtime/ExecutionLedger.js.map +1 -0
  568. package/dist/runtime/FinalReportGuard.d.ts +16 -0
  569. package/dist/runtime/FinalReportGuard.js +14 -0
  570. package/dist/runtime/FinalReportGuard.js.map +1 -0
  571. package/dist/runtime/ModelUsageLedger.d.ts +101 -0
  572. package/dist/runtime/ModelUsageLedger.js +296 -0
  573. package/dist/runtime/ModelUsageLedger.js.map +1 -0
  574. package/dist/runtime/RuntimeDoctor.d.ts +23 -0
  575. package/dist/runtime/RuntimeDoctor.js +151 -0
  576. package/dist/runtime/RuntimeDoctor.js.map +1 -0
  577. package/dist/runtime/RuntimeEvidenceLedger.d.ts +50 -0
  578. package/dist/runtime/RuntimeEvidenceLedger.js +89 -0
  579. package/dist/runtime/RuntimeEvidenceLedger.js.map +1 -0
  580. package/dist/runtime/SessionLedger.d.ts +53 -0
  581. package/dist/runtime/SessionLedger.js +104 -0
  582. package/dist/runtime/SessionLedger.js.map +1 -0
  583. package/dist/runtime/index.d.ts +7 -0
  584. package/dist/runtime/index.js +8 -0
  585. package/dist/runtime/index.js.map +1 -0
  586. package/dist/setup/SetupVerification.d.ts +42 -0
  587. package/dist/setup/SetupVerification.js +180 -0
  588. package/dist/setup/SetupVerification.js.map +1 -0
  589. package/dist/setup/SetupWizard.d.ts +45 -0
  590. package/dist/setup/SetupWizard.js +216 -0
  591. package/dist/setup/SetupWizard.js.map +1 -0
  592. package/dist/shield/PolicyCompiler.d.ts +70 -0
  593. package/dist/shield/PolicyCompiler.js +540 -0
  594. package/dist/shield/PolicyCompiler.js.map +1 -0
  595. package/dist/shield/ProtectedPaths.d.ts +39 -0
  596. package/dist/shield/ProtectedPaths.js +179 -0
  597. package/dist/shield/ProtectedPaths.js.map +1 -0
  598. package/dist/shield/ShieldProtocol.d.ts +50 -0
  599. package/dist/shield/ShieldProtocol.js +103 -0
  600. package/dist/shield/ShieldProtocol.js.map +1 -0
  601. package/dist/skills/ExternalSkills.d.ts +3 -0
  602. package/dist/skills/ExternalSkills.js +27 -0
  603. package/dist/skills/ExternalSkills.js.map +1 -0
  604. package/dist/skills/GrillingSessionSkill.d.ts +65 -0
  605. package/dist/skills/GrillingSessionSkill.js +113 -0
  606. package/dist/skills/GrillingSessionSkill.js.map +1 -0
  607. package/dist/skills/GrillingTemplates.d.ts +7 -0
  608. package/dist/skills/GrillingTemplates.js +38 -0
  609. package/dist/skills/GrillingTemplates.js.map +1 -0
  610. package/dist/skills/RoleSkills.d.ts +20 -0
  611. package/dist/skills/RoleSkills.js +154 -0
  612. package/dist/skills/RoleSkills.js.map +1 -0
  613. package/dist/skills/SkillCatalog.d.ts +13 -0
  614. package/dist/skills/SkillCatalog.js +184 -0
  615. package/dist/skills/SkillCatalog.js.map +1 -0
  616. package/dist/skills/SkillDiscovery.d.ts +84 -0
  617. package/dist/skills/SkillDiscovery.js +402 -0
  618. package/dist/skills/SkillDiscovery.js.map +1 -0
  619. package/dist/skills/SkillDoctor.d.ts +37 -0
  620. package/dist/skills/SkillDoctor.js +267 -0
  621. package/dist/skills/SkillDoctor.js.map +1 -0
  622. package/dist/skills/SkillExecutor.d.ts +38 -0
  623. package/dist/skills/SkillExecutor.js +237 -0
  624. package/dist/skills/SkillExecutor.js.map +1 -0
  625. package/dist/skills/SkillFrontmatter.d.ts +28 -0
  626. package/dist/skills/SkillFrontmatter.js +152 -0
  627. package/dist/skills/SkillFrontmatter.js.map +1 -0
  628. package/dist/skills/SkillInstaller.d.ts +40 -0
  629. package/dist/skills/SkillInstaller.js +117 -0
  630. package/dist/skills/SkillInstaller.js.map +1 -0
  631. package/dist/skills/SkillMdStandard.d.ts +33 -0
  632. package/dist/skills/SkillMdStandard.js +88 -0
  633. package/dist/skills/SkillMdStandard.js.map +1 -0
  634. package/dist/skills/SkillRadar.d.ts +83 -0
  635. package/dist/skills/SkillRadar.js +404 -0
  636. package/dist/skills/SkillRadar.js.map +1 -0
  637. package/dist/skills/SkillRegistry.d.ts +112 -0
  638. package/dist/skills/SkillRegistry.js +161 -0
  639. package/dist/skills/SkillRegistry.js.map +1 -0
  640. package/dist/skills/SkillRepository.d.ts +71 -0
  641. package/dist/skills/SkillRepository.js +435 -0
  642. package/dist/skills/SkillRepository.js.map +1 -0
  643. package/dist/skills/TriggerEngine.d.ts +43 -0
  644. package/dist/skills/TriggerEngine.js +142 -0
  645. package/dist/skills/TriggerEngine.js.map +1 -0
  646. package/dist/skills/coreSkills.d.ts +6 -0
  647. package/dist/skills/coreSkills.js +41 -0
  648. package/dist/skills/coreSkills.js.map +1 -0
  649. package/dist/skills/index.d.ts +10 -0
  650. package/dist/skills/index.js +12 -0
  651. package/dist/skills/index.js.map +1 -0
  652. package/dist/skills/interop/GStackInterop.d.ts +15 -0
  653. package/dist/skills/interop/GStackInterop.js +34 -0
  654. package/dist/skills/interop/GStackInterop.js.map +1 -0
  655. package/dist/skills/interop/OMCInterop.d.ts +15 -0
  656. package/dist/skills/interop/OMCInterop.js +34 -0
  657. package/dist/skills/interop/OMCInterop.js.map +1 -0
  658. package/dist/skills/routing/SkillGate.d.ts +12 -0
  659. package/dist/skills/routing/SkillGate.js +117 -0
  660. package/dist/skills/routing/SkillGate.js.map +1 -0
  661. package/dist/skills/routing/SkillPlanner.d.ts +8 -0
  662. package/dist/skills/routing/SkillPlanner.js +179 -0
  663. package/dist/skills/routing/SkillPlanner.js.map +1 -0
  664. package/dist/skills/routing/SkillPolicy.d.ts +6 -0
  665. package/dist/skills/routing/SkillPolicy.js +336 -0
  666. package/dist/skills/routing/SkillPolicy.js.map +1 -0
  667. package/dist/skills/routing/SkillRoutingTypes.d.ts +89 -0
  668. package/dist/skills/routing/SkillRoutingTypes.js +2 -0
  669. package/dist/skills/routing/SkillRoutingTypes.js.map +1 -0
  670. package/dist/skills/routing/TaskIntentClassifier.d.ts +6 -0
  671. package/dist/skills/routing/TaskIntentClassifier.js +79 -0
  672. package/dist/skills/routing/TaskIntentClassifier.js.map +1 -0
  673. package/dist/skills/routing/index.d.ts +5 -0
  674. package/dist/skills/routing/index.js +6 -0
  675. package/dist/skills/routing/index.js.map +1 -0
  676. package/dist/tasks/IssueTriageFSM.d.ts +26 -0
  677. package/dist/tasks/IssueTriageFSM.js +107 -0
  678. package/dist/tasks/IssueTriageFSM.js.map +1 -0
  679. package/dist/tasks/TaskEngine.d.ts +97 -0
  680. package/dist/tasks/TaskEngine.js +289 -0
  681. package/dist/tasks/TaskEngine.js.map +1 -0
  682. package/dist/testing/DiffTestSelector.d.ts +22 -0
  683. package/dist/testing/DiffTestSelector.js +114 -0
  684. package/dist/testing/DiffTestSelector.js.map +1 -0
  685. package/dist/testing/index.d.ts +1 -0
  686. package/dist/testing/index.js +3 -0
  687. package/dist/testing/index.js.map +1 -0
  688. package/dist/tools/CommandOutputCompressor.d.ts +28 -0
  689. package/dist/tools/CommandOutputCompressor.js +242 -0
  690. package/dist/tools/CommandOutputCompressor.js.map +1 -0
  691. package/dist/tools/CommandRunLedger.d.ts +77 -0
  692. package/dist/tools/CommandRunLedger.js +111 -0
  693. package/dist/tools/CommandRunLedger.js.map +1 -0
  694. package/dist/tools/RtkRuntime.d.ts +9 -0
  695. package/dist/tools/RtkRuntime.js +43 -0
  696. package/dist/tools/RtkRuntime.js.map +1 -0
  697. package/dist/tools/SafeCommandRunner.d.ts +16 -0
  698. package/dist/tools/SafeCommandRunner.js +83 -0
  699. package/dist/tools/SafeCommandRunner.js.map +1 -0
  700. package/dist/tools/ToolCapabilityRegistry.d.ts +51 -0
  701. package/dist/tools/ToolCapabilityRegistry.js +295 -0
  702. package/dist/tools/ToolCapabilityRegistry.js.map +1 -0
  703. package/dist/tools/ToolEvidenceGate.d.ts +39 -0
  704. package/dist/tools/ToolEvidenceGate.js +117 -0
  705. package/dist/tools/ToolEvidenceGate.js.map +1 -0
  706. package/dist/tools/ToolEvidenceStore.d.ts +58 -0
  707. package/dist/tools/ToolEvidenceStore.js +129 -0
  708. package/dist/tools/ToolEvidenceStore.js.map +1 -0
  709. package/dist/tools/ToolOrchestrator.d.ts +67 -0
  710. package/dist/tools/ToolOrchestrator.js +252 -0
  711. package/dist/tools/ToolOrchestrator.js.map +1 -0
  712. package/dist/tools/ToolPolicy.d.ts +33 -0
  713. package/dist/tools/ToolPolicy.js +172 -0
  714. package/dist/tools/ToolPolicy.js.map +1 -0
  715. package/dist/tools/index.d.ts +7 -0
  716. package/dist/tools/index.js +8 -0
  717. package/dist/tools/index.js.map +1 -0
  718. package/dist/tui/TuiDashboard.d.ts +3 -0
  719. package/dist/tui/TuiDashboard.js +120 -0
  720. package/dist/tui/TuiDashboard.js.map +1 -0
  721. package/dist/version.d.ts +3 -0
  722. package/dist/version.js +15 -0
  723. package/dist/version.js.map +1 -0
  724. package/dist/workflow/AdaptiveWorkflowRouter.d.ts +38 -0
  725. package/dist/workflow/AdaptiveWorkflowRouter.js +214 -0
  726. package/dist/workflow/AdaptiveWorkflowRouter.js.map +1 -0
  727. package/dist/workflow/CommitDiscipline.d.ts +68 -0
  728. package/dist/workflow/CommitDiscipline.js +328 -0
  729. package/dist/workflow/CommitDiscipline.js.map +1 -0
  730. package/dist/workflow/ContextGovernance.d.ts +51 -0
  731. package/dist/workflow/ContextGovernance.js +233 -0
  732. package/dist/workflow/ContextGovernance.js.map +1 -0
  733. package/dist/workflow/CrossRepoOrchestrator.d.ts +92 -0
  734. package/dist/workflow/CrossRepoOrchestrator.js +408 -0
  735. package/dist/workflow/CrossRepoOrchestrator.js.map +1 -0
  736. package/dist/workflow/DiagnosticLoop.d.ts +40 -0
  737. package/dist/workflow/DiagnosticLoop.js +105 -0
  738. package/dist/workflow/DiagnosticLoop.js.map +1 -0
  739. package/dist/workflow/EngineeringStandards.d.ts +212 -0
  740. package/dist/workflow/EngineeringStandards.js +1113 -0
  741. package/dist/workflow/EngineeringStandards.js.map +1 -0
  742. package/dist/workflow/EvidenceStore.d.ts +20 -0
  743. package/dist/workflow/EvidenceStore.js +48 -0
  744. package/dist/workflow/EvidenceStore.js.map +1 -0
  745. package/dist/workflow/EvolutionShadowPromoter.d.ts +46 -0
  746. package/dist/workflow/EvolutionShadowPromoter.js +73 -0
  747. package/dist/workflow/EvolutionShadowPromoter.js.map +1 -0
  748. package/dist/workflow/GateCatalog.d.ts +63 -0
  749. package/dist/workflow/GateCatalog.js +268 -0
  750. package/dist/workflow/GateCatalog.js.map +1 -0
  751. package/dist/workflow/GovernanceLock.d.ts +35 -0
  752. package/dist/workflow/GovernanceLock.js +58 -0
  753. package/dist/workflow/GovernanceLock.js.map +1 -0
  754. package/dist/workflow/GovernanceRoi.d.ts +52 -0
  755. package/dist/workflow/GovernanceRoi.js +204 -0
  756. package/dist/workflow/GovernanceRoi.js.map +1 -0
  757. package/dist/workflow/GovernanceTemplatePacks.d.ts +24 -0
  758. package/dist/workflow/GovernanceTemplatePacks.js +2134 -0
  759. package/dist/workflow/GovernanceTemplatePacks.js.map +1 -0
  760. package/dist/workflow/GovernanceTemplates.d.ts +19 -0
  761. package/dist/workflow/GovernanceTemplates.js +1291 -0
  762. package/dist/workflow/GovernanceTemplates.js.map +1 -0
  763. package/dist/workflow/McpGovernance.d.ts +63 -0
  764. package/dist/workflow/McpGovernance.js +198 -0
  765. package/dist/workflow/McpGovernance.js.map +1 -0
  766. package/dist/workflow/OutOfScopeStore.d.ts +37 -0
  767. package/dist/workflow/OutOfScopeStore.js +164 -0
  768. package/dist/workflow/OutOfScopeStore.js.map +1 -0
  769. package/dist/workflow/PhaseMarkerTracker.d.ts +63 -0
  770. package/dist/workflow/PhaseMarkerTracker.js +291 -0
  771. package/dist/workflow/PhaseMarkerTracker.js.map +1 -0
  772. package/dist/workflow/ResourceGovernance.d.ts +120 -0
  773. package/dist/workflow/ResourceGovernance.js +531 -0
  774. package/dist/workflow/ResourceGovernance.js.map +1 -0
  775. package/dist/workflow/ReviewAnalyzer.d.ts +80 -0
  776. package/dist/workflow/ReviewAnalyzer.js +438 -0
  777. package/dist/workflow/ReviewAnalyzer.js.map +1 -0
  778. package/dist/workflow/ReviewStore.d.ts +36 -0
  779. package/dist/workflow/ReviewStore.js +42 -0
  780. package/dist/workflow/ReviewStore.js.map +1 -0
  781. package/dist/workflow/SecurityAudit.d.ts +27 -0
  782. package/dist/workflow/SecurityAudit.js +294 -0
  783. package/dist/workflow/SecurityAudit.js.map +1 -0
  784. package/dist/workflow/SessionCoordinator.d.ts +103 -0
  785. package/dist/workflow/SessionCoordinator.js +401 -0
  786. package/dist/workflow/SessionCoordinator.js.map +1 -0
  787. package/dist/workflow/SessionPreamble.d.ts +19 -0
  788. package/dist/workflow/SessionPreamble.js +130 -0
  789. package/dist/workflow/SessionPreamble.js.map +1 -0
  790. package/dist/workflow/SessionStateTracker.d.ts +74 -0
  791. package/dist/workflow/SessionStateTracker.js +270 -0
  792. package/dist/workflow/SessionStateTracker.js.map +1 -0
  793. package/dist/workflow/ShipPipeline.d.ts +30 -0
  794. package/dist/workflow/ShipPipeline.js +366 -0
  795. package/dist/workflow/ShipPipeline.js.map +1 -0
  796. package/dist/workflow/TaskArtifactScaffolder.d.ts +69 -0
  797. package/dist/workflow/TaskArtifactScaffolder.js +333 -0
  798. package/dist/workflow/TaskArtifactScaffolder.js.map +1 -0
  799. package/dist/workflow/TaskDependencyGraph.d.ts +73 -0
  800. package/dist/workflow/TaskDependencyGraph.js +245 -0
  801. package/dist/workflow/TaskDependencyGraph.js.map +1 -0
  802. package/dist/workflow/TaskLevelDetector.d.ts +41 -0
  803. package/dist/workflow/TaskLevelDetector.js +219 -0
  804. package/dist/workflow/TaskLevelDetector.js.map +1 -0
  805. package/dist/workflow/TaskMetricsStore.d.ts +49 -0
  806. package/dist/workflow/TaskMetricsStore.js +149 -0
  807. package/dist/workflow/TaskMetricsStore.js.map +1 -0
  808. package/dist/workflow/TaskScoreEngine.d.ts +42 -0
  809. package/dist/workflow/TaskScoreEngine.js +181 -0
  810. package/dist/workflow/TaskScoreEngine.js.map +1 -0
  811. package/dist/workflow/TddLoop.d.ts +49 -0
  812. package/dist/workflow/TddLoop.js +78 -0
  813. package/dist/workflow/TddLoop.js.map +1 -0
  814. package/dist/workflow/UpgradeManager.d.ts +178 -0
  815. package/dist/workflow/UpgradeManager.js +665 -0
  816. package/dist/workflow/UpgradeManager.js.map +1 -0
  817. package/dist/workflow/VerificationCommands.d.ts +36 -0
  818. package/dist/workflow/VerificationCommands.js +123 -0
  819. package/dist/workflow/VerificationCommands.js.map +1 -0
  820. package/dist/workflow/VerificationProfile.d.ts +67 -0
  821. package/dist/workflow/VerificationProfile.js +241 -0
  822. package/dist/workflow/VerificationProfile.js.map +1 -0
  823. package/dist/workflow/VerificationSchema.d.ts +46 -0
  824. package/dist/workflow/VerificationSchema.js +97 -0
  825. package/dist/workflow/VerificationSchema.js.map +1 -0
  826. package/dist/workflow/WorkflowArtifactWriter.d.ts +113 -0
  827. package/dist/workflow/WorkflowArtifactWriter.js +242 -0
  828. package/dist/workflow/WorkflowArtifactWriter.js.map +1 -0
  829. package/dist/workflow/WorkflowEngine.d.ts +83 -0
  830. package/dist/workflow/WorkflowEngine.js +183 -0
  831. package/dist/workflow/WorkflowEngine.js.map +1 -0
  832. package/dist/workflow/WorkflowGuidance.d.ts +30 -0
  833. package/dist/workflow/WorkflowGuidance.js +204 -0
  834. package/dist/workflow/WorkflowGuidance.js.map +1 -0
  835. package/dist/workflow/WorkflowOpenTasks.d.ts +16 -0
  836. package/dist/workflow/WorkflowOpenTasks.js +37 -0
  837. package/dist/workflow/WorkflowOpenTasks.js.map +1 -0
  838. package/dist/workflow/WorkflowOrchestrator.d.ts +59 -0
  839. package/dist/workflow/WorkflowOrchestrator.js +326 -0
  840. package/dist/workflow/WorkflowOrchestrator.js.map +1 -0
  841. package/dist/workflow/WorkflowTemplates.d.ts +38 -0
  842. package/dist/workflow/WorkflowTemplates.js +371 -0
  843. package/dist/workflow/WorkflowTemplates.js.map +1 -0
  844. package/dist/workflow/WorkspaceLifecycle.d.ts +71 -0
  845. package/dist/workflow/WorkspaceLifecycle.js +401 -0
  846. package/dist/workflow/WorkspaceLifecycle.js.map +1 -0
  847. package/dist/workflow/WorkspacePolicy.d.ts +46 -0
  848. package/dist/workflow/WorkspacePolicy.js +141 -0
  849. package/dist/workflow/WorkspacePolicy.js.map +1 -0
  850. package/dist/workflow/WorkspaceSafety.d.ts +9 -0
  851. package/dist/workflow/WorkspaceSafety.js +49 -0
  852. package/dist/workflow/WorkspaceSafety.js.map +1 -0
  853. package/dist/workflow/WorkspaceTopology.d.ts +58 -0
  854. package/dist/workflow/WorkspaceTopology.js +176 -0
  855. package/dist/workflow/WorkspaceTopology.js.map +1 -0
  856. package/dist/workflow/autofix/AutoFixEngine.d.ts +37 -0
  857. package/dist/workflow/autofix/AutoFixEngine.js +169 -0
  858. package/dist/workflow/autofix/AutoFixEngine.js.map +1 -0
  859. package/dist/workflow/autonomous/AutonomousDevLoop.d.ts +88 -0
  860. package/dist/workflow/autonomous/AutonomousDevLoop.js +381 -0
  861. package/dist/workflow/autonomous/AutonomousDevLoop.js.map +1 -0
  862. package/dist/workflow/autonomous/BackgroundHunter.d.ts +74 -0
  863. package/dist/workflow/autonomous/BackgroundHunter.js +220 -0
  864. package/dist/workflow/autonomous/BackgroundHunter.js.map +1 -0
  865. package/dist/workflow/autonomous/WorklogManager.d.ts +50 -0
  866. package/dist/workflow/autonomous/WorklogManager.js +264 -0
  867. package/dist/workflow/autonomous/WorklogManager.js.map +1 -0
  868. package/dist/workflow/autonomous/index.d.ts +3 -0
  869. package/dist/workflow/autonomous/index.js +5 -0
  870. package/dist/workflow/autonomous/index.js.map +1 -0
  871. package/dist/workflow/cognitive/AmbiguityScorer.d.ts +17 -0
  872. package/dist/workflow/cognitive/AmbiguityScorer.js +107 -0
  873. package/dist/workflow/cognitive/AmbiguityScorer.js.map +1 -0
  874. package/dist/workflow/cognitive/ConsensusPlanner.d.ts +26 -0
  875. package/dist/workflow/cognitive/ConsensusPlanner.js +141 -0
  876. package/dist/workflow/cognitive/ConsensusPlanner.js.map +1 -0
  877. package/dist/workflow/cognitive/SocraticQuestioner.d.ts +33 -0
  878. package/dist/workflow/cognitive/SocraticQuestioner.js +276 -0
  879. package/dist/workflow/cognitive/SocraticQuestioner.js.map +1 -0
  880. package/dist/workflow/evolution/LessonExtractor.d.ts +90 -0
  881. package/dist/workflow/evolution/LessonExtractor.js +317 -0
  882. package/dist/workflow/evolution/LessonExtractor.js.map +1 -0
  883. package/dist/workflow/evolution/SelfImproveEngine.d.ts +156 -0
  884. package/dist/workflow/evolution/SelfImproveEngine.js +361 -0
  885. package/dist/workflow/evolution/SelfImproveEngine.js.map +1 -0
  886. package/dist/workflow/execution/RalphEngine.d.ts +54 -0
  887. package/dist/workflow/execution/RalphEngine.js +145 -0
  888. package/dist/workflow/execution/RalphEngine.js.map +1 -0
  889. package/dist/workflow/execution/UltraworkEngine.d.ts +43 -0
  890. package/dist/workflow/execution/UltraworkEngine.js +135 -0
  891. package/dist/workflow/execution/UltraworkEngine.js.map +1 -0
  892. package/dist/workflow/gates/EnhancedGates.d.ts +74 -0
  893. package/dist/workflow/gates/EnhancedGates.js +653 -0
  894. package/dist/workflow/gates/EnhancedGates.js.map +1 -0
  895. package/dist/workflow/gates/GateSystem.d.ts +180 -0
  896. package/dist/workflow/gates/GateSystem.js +1279 -0
  897. package/dist/workflow/gates/GateSystem.js.map +1 -0
  898. package/dist/workflow/gates/MetaGovernanceGates.d.ts +70 -0
  899. package/dist/workflow/gates/MetaGovernanceGates.js +617 -0
  900. package/dist/workflow/gates/MetaGovernanceGates.js.map +1 -0
  901. package/dist/workflow/gates/VisualGate.d.ts +41 -0
  902. package/dist/workflow/gates/VisualGate.js +174 -0
  903. package/dist/workflow/gates/VisualGate.js.map +1 -0
  904. package/dist/workflow/index.d.ts +45 -0
  905. package/dist/workflow/index.js +47 -0
  906. package/dist/workflow/index.js.map +1 -0
  907. package/dist/workflow/qa/E2ETestRunner.d.ts +102 -0
  908. package/dist/workflow/qa/E2ETestRunner.js +227 -0
  909. package/dist/workflow/qa/E2ETestRunner.js.map +1 -0
  910. package/dist/workflow/quality/HonestDelivery.d.ts +19 -0
  911. package/dist/workflow/quality/HonestDelivery.js +77 -0
  912. package/dist/workflow/quality/HonestDelivery.js.map +1 -0
  913. package/dist/workflow/quality/KarpathyEvaluator.d.ts +18 -0
  914. package/dist/workflow/quality/KarpathyEvaluator.js +76 -0
  915. package/dist/workflow/quality/KarpathyEvaluator.js.map +1 -0
  916. package/dist/workflow/types.d.ts +151 -0
  917. package/dist/workflow/types.js +4 -0
  918. package/dist/workflow/types.js.map +1 -0
  919. package/dist/workflows/DAGBuilder.d.ts +52 -0
  920. package/dist/workflows/DAGBuilder.js +169 -0
  921. package/dist/workflows/DAGBuilder.js.map +1 -0
  922. package/dist/workflows/GateParser.d.ts +55 -0
  923. package/dist/workflows/GateParser.js +73 -0
  924. package/dist/workflows/GateParser.js.map +1 -0
  925. package/dist/workflows/WorkflowExecutor.d.ts +56 -0
  926. package/dist/workflows/WorkflowExecutor.js +143 -0
  927. package/dist/workflows/WorkflowExecutor.js.map +1 -0
  928. package/dist/workflows/WorkflowOrchestrator.d.ts +81 -0
  929. package/dist/workflows/WorkflowOrchestrator.js +337 -0
  930. package/dist/workflows/WorkflowOrchestrator.js.map +1 -0
  931. package/dist/workflows/index.d.ts +2 -0
  932. package/dist/workflows/index.js +5 -0
  933. package/dist/workflows/index.js.map +1 -0
  934. package/dist/workflows/presets.d.ts +34 -0
  935. package/dist/workflows/presets.js +224 -0
  936. package/dist/workflows/presets.js.map +1 -0
  937. package/docs/README.md +105 -0
  938. package/docs/guides/DEVELOPMENT_WORKFLOW.md +99 -0
  939. package/docs/guides/GETTING_STARTED.md +93 -0
  940. package/docs/guides/MEDICAL_AGENT_OPERATING_GUIDE.md +61 -0
  941. package/docs/guides/MEDICAL_RESEARCH_DELIVERY.md +217 -0
  942. package/docs/guides/MIGRATION.md +119 -0
  943. package/docs/reference/cli.md +2921 -0
  944. package/docs/start/README.md +79 -0
  945. package/docs/start/agent-governance-demo.md +107 -0
  946. package/docs/start/artifact-lifecycle.md +326 -0
  947. package/docs/start/quickstart.md +191 -0
  948. package/docs/start/workflow-upgrade.md +198 -0
  949. package/docs/workflow/GATES_AND_SCORE.md +89 -0
  950. package/docs/workflow/PROMPT_OPTIMIZATION.md +44 -0
  951. package/docs/workflow/README.md +123 -0
  952. package/docs/workflow/node-library.md +52 -0
  953. package/docs/workflow/templates/api-contract.md +29 -0
  954. package/docs/workflow/templates/architecture-review.md +23 -0
  955. package/docs/workflow/templates/db-change-plan.md +20 -0
  956. package/docs/workflow/templates/docs-impact.md +17 -0
  957. package/docs/workflow/templates/e2e-plan.md +20 -0
  958. package/docs/workflow/templates/explore.md +16 -0
  959. package/docs/workflow/templates/github-actions-scale-preflight.yml +32 -0
  960. package/docs/workflow/templates/mini-prd.md +16 -0
  961. package/docs/workflow/templates/plan.md +37 -0
  962. package/docs/workflow/templates/pre-push-scale-preflight.sh +8 -0
  963. package/docs/workflow/templates/product-smoke.md +61 -0
  964. package/docs/workflow/templates/reality-check.md +28 -0
  965. package/docs/workflow/templates/resource-cleanup.md +17 -0
  966. package/docs/workflow/templates/resource-impact.md +25 -0
  967. package/docs/workflow/templates/review.md +12 -0
  968. package/docs/workflow/templates/runtime.md +23 -0
  969. package/docs/workflow/templates/security-review.md +26 -0
  970. package/docs/workflow/templates/skill-evidence.md +33 -0
  971. package/docs/workflow/templates/skill-plan.md +39 -0
  972. package/docs/workflow/templates/spec.md +17 -0
  973. package/docs/workflow/templates/standards-impact.md +28 -0
  974. package/docs/workflow/templates/summary.md +16 -0
  975. package/docs/workflow/templates/tasks.md +8 -0
  976. package/docs/workflow/templates/ui-spec.md +29 -0
  977. package/docs/workflow/templates/verification.md +20 -0
  978. package/docs/workflow/templates/visual-review.md +20 -0
  979. package/docs/zh/quickstart.md +108 -0
  980. package/examples/demo-projects/agent-governance-demo/CONTEXT.md +14 -0
  981. package/examples/demo-projects/agent-governance-demo/README.md +48 -0
  982. package/examples/demo-projects/agent-governance-demo/docs/CONTEXT-MAP.md +14 -0
  983. package/examples/demo-projects/agent-governance-demo/package.json +22 -0
  984. package/examples/demo-projects/agent-governance-demo/src/oauth-state.ts +39 -0
  985. package/examples/demo-projects/agent-governance-demo/tests/oauth-state.test.ts +52 -0
  986. package/mcp-configs/_INDEX.md +55 -0
  987. package/mcp-configs/context7/config.json +9 -0
  988. package/mcp-configs/fetch/config.json +9 -0
  989. package/mcp-configs/filesystem/config.json +9 -0
  990. package/mcp-configs/github/config.json +11 -0
  991. package/mcp-configs/memory/config.json +9 -0
  992. package/mcp-configs/neon/config.json +11 -0
  993. package/mcp-configs/playwright/config.json +9 -0
  994. package/mcp-configs/postgres/config.json +11 -0
  995. package/mcp-configs/puppeteer/config.json +9 -0
  996. package/mcp-configs/sequential-thinking/config.json +9 -0
  997. package/package.json +113 -0
  998. package/scripts/workflow/lib/gbrain-runtime.mjs +185 -0
  999. package/scripts/workflow/lib/report-output.mjs +107 -0
  1000. package/scripts/workflow/medscale-release-smoke.mjs +338 -0
  1001. package/scripts/workflow/provider-rehearsal.mjs +597 -0
  1002. package/scripts/workflow/setup-smoke.mjs +433 -0
  1003. package/target-research-platform/bridge_runner.py +310 -0
  1004. package/target-research-platform/config.yaml +148 -0
  1005. package/target-research-platform/data/immune_infiltration/ACC.csv +201 -0
  1006. package/target-research-platform/data/immune_infiltration/BLCA.csv +201 -0
  1007. package/target-research-platform/data/immune_infiltration/BRCA.csv +201 -0
  1008. package/target-research-platform/data/immune_infiltration/CESC.csv +201 -0
  1009. package/target-research-platform/data/immune_infiltration/CHOL.csv +201 -0
  1010. package/target-research-platform/data/immune_infiltration/COAD.csv +201 -0
  1011. package/target-research-platform/data/immune_infiltration/DLBC.csv +201 -0
  1012. package/target-research-platform/data/immune_infiltration/ESCA.csv +201 -0
  1013. package/target-research-platform/data/immune_infiltration/GBM.csv +201 -0
  1014. package/target-research-platform/data/immune_infiltration/HNSC.csv +201 -0
  1015. package/target-research-platform/data/immune_infiltration/KICH.csv +201 -0
  1016. package/target-research-platform/data/immune_infiltration/KIRC.csv +201 -0
  1017. package/target-research-platform/data/immune_infiltration/KIRP.csv +201 -0
  1018. package/target-research-platform/data/immune_infiltration/LAML.csv +201 -0
  1019. package/target-research-platform/data/immune_infiltration/LGG.csv +201 -0
  1020. package/target-research-platform/data/immune_infiltration/LIHC.csv +201 -0
  1021. package/target-research-platform/data/immune_infiltration/LUAD.csv +201 -0
  1022. package/target-research-platform/data/immune_infiltration/LUSC.csv +201 -0
  1023. package/target-research-platform/data/immune_infiltration/MESO.csv +201 -0
  1024. package/target-research-platform/data/immune_infiltration/OV.csv +201 -0
  1025. package/target-research-platform/data/immune_infiltration/PAAD.csv +201 -0
  1026. package/target-research-platform/data/immune_infiltration/PCPG.csv +201 -0
  1027. package/target-research-platform/data/immune_infiltration/PRAD.csv +201 -0
  1028. package/target-research-platform/data/immune_infiltration/READ.csv +201 -0
  1029. package/target-research-platform/data/immune_infiltration/SARC.csv +201 -0
  1030. package/target-research-platform/data/immune_infiltration/SKCM.csv +201 -0
  1031. package/target-research-platform/data/immune_infiltration/STAD.csv +201 -0
  1032. package/target-research-platform/data/immune_infiltration/TGCT.csv +201 -0
  1033. package/target-research-platform/data/immune_infiltration/THCA.csv +201 -0
  1034. package/target-research-platform/data/immune_infiltration/THYM.csv +201 -0
  1035. package/target-research-platform/data/immune_infiltration/UCEC.csv +201 -0
  1036. package/target-research-platform/data/immune_infiltration/UCS.csv +201 -0
  1037. package/target-research-platform/data/immune_infiltration/UVM.csv +201 -0
  1038. package/target-research-platform/docs/JSON_SCHEMA.md +352 -0
  1039. package/target-research-platform/docs/PROGRESS_2026-06-09.md +140 -0
  1040. package/target-research-platform/main.py +755 -0
  1041. package/target-research-platform/pyproject.toml +54 -0
  1042. package/target-research-platform/requirements.txt +9 -0
  1043. package/target-research-platform/run_dashboard.py +5 -0
  1044. package/target-research-platform/run_real_medical_workflow.py +28 -0
  1045. package/target-research-platform/scripts/demo_report.py +481 -0
  1046. package/target-research-platform/scripts/generate_evidence_package.py +75 -0
  1047. package/target-research-platform/scripts/prepare_immune_data.py +386 -0
  1048. package/target-research-platform/src/__init__.py +4 -0
  1049. package/target-research-platform/src/analysis/__init__.py +3 -0
  1050. package/target-research-platform/src/analysis/stage1_expression/__init__.py +12 -0
  1051. package/target-research-platform/src/analysis/stage1_expression/immune_correlation.py +366 -0
  1052. package/target-research-platform/src/analysis/stage1_expression/pipeline.py +579 -0
  1053. package/target-research-platform/src/analysis/stage1_expression/survival_analysis.py +382 -0
  1054. package/target-research-platform/src/analysis/stage2_screening/__init__.py +12 -0
  1055. package/target-research-platform/src/analysis/stage2_screening/differential_expression.py +451 -0
  1056. package/target-research-platform/src/analysis/stage2_screening/druggability_score.py +477 -0
  1057. package/target-research-platform/src/analysis/stage2_screening/pipeline.py +822 -0
  1058. package/target-research-platform/src/analysis/stage3_deep_research/__init__.py +12 -0
  1059. package/target-research-platform/src/analysis/stage3_deep_research/competitive_landscape.py +479 -0
  1060. package/target-research-platform/src/analysis/stage3_deep_research/pipeline.py +763 -0
  1061. package/target-research-platform/src/analysis/stage3_deep_research/target_function.py +290 -0
  1062. package/target-research-platform/src/analysis/stage4_business/__init__.py +12 -0
  1063. package/target-research-platform/src/analysis/stage4_business/business_plan.py +816 -0
  1064. package/target-research-platform/src/analysis/stage4_business/milestone_planner.py +844 -0
  1065. package/target-research-platform/src/analysis/stage4_business/pipeline.py +284 -0
  1066. package/target-research-platform/src/api_clients/__init__.py +29 -0
  1067. package/target-research-platform/src/api_clients/aminer_client.py +163 -0
  1068. package/target-research-platform/src/api_clients/base_client.py +186 -0
  1069. package/target-research-platform/src/api_clients/clinicaltrials_client.py +411 -0
  1070. package/target-research-platform/src/api_clients/geo_client.py +1420 -0
  1071. package/target-research-platform/src/api_clients/gtex_client.py +209 -0
  1072. package/target-research-platform/src/api_clients/hpa_client.py +170 -0
  1073. package/target-research-platform/src/api_clients/immune_data_manager.py +247 -0
  1074. package/target-research-platform/src/api_clients/openalex_client.py +86 -0
  1075. package/target-research-platform/src/api_clients/opentargets_client.py +558 -0
  1076. package/target-research-platform/src/api_clients/pubmed_client.py +361 -0
  1077. package/target-research-platform/src/api_clients/tcga_client.py +712 -0
  1078. package/target-research-platform/src/api_clients/timer_client.py +169 -0
  1079. package/target-research-platform/src/dashboard/app.py +2283 -0
  1080. package/target-research-platform/src/dashboard/deliverables.py +710 -0
  1081. package/target-research-platform/src/dashboard/static/artifactPreview.js +96 -0
  1082. package/target-research-platform/src/dashboard/static/index.html +1292 -0
  1083. package/target-research-platform/src/dashboard/status_tracker.py +237 -0
  1084. package/target-research-platform/src/output/__init__.py +42 -0
  1085. package/target-research-platform/src/output/agent_llm_provider.py +171 -0
  1086. package/target-research-platform/src/output/chart_generator.py +550 -0
  1087. package/target-research-platform/src/output/data_exporter.py +215 -0
  1088. package/target-research-platform/src/output/delivery_packager.py +791 -0
  1089. package/target-research-platform/src/output/evidence_package.py +230 -0
  1090. package/target-research-platform/src/output/journal_templates.py +193 -0
  1091. package/target-research-platform/src/output/literature_enricher.py +395 -0
  1092. package/target-research-platform/src/output/literature_reviewer.py +420 -0
  1093. package/target-research-platform/src/output/manuscript_fact_checker.py +279 -0
  1094. package/target-research-platform/src/output/manuscript_generator.py +1189 -0
  1095. package/target-research-platform/src/output/manuscript_quality.py +401 -0
  1096. package/target-research-platform/src/output/medical_skills_bridge.py +140 -0
  1097. package/target-research-platform/src/output/report_generator.py +542 -0
  1098. package/target-research-platform/src/output/usage_envelope.py +193 -0
  1099. package/target-research-platform/src/utils/__init__.py +1 -0
  1100. package/target-research-platform/src/utils/config.py +125 -0
  1101. package/target-research-platform/src/utils/logger.py +71 -0
  1102. package/target-research-platform/src/validators/__init__.py +13 -0
  1103. package/target-research-platform/src/validators/cross_validator.py +394 -0
  1104. 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()