oscura 0.5.1__py3-none-any.whl → 0.7.0__py3-none-any.whl

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 (497) hide show
  1. oscura/__init__.py +169 -167
  2. oscura/analyzers/__init__.py +3 -0
  3. oscura/analyzers/classification.py +659 -0
  4. oscura/analyzers/digital/edges.py +325 -65
  5. oscura/analyzers/digital/quality.py +293 -166
  6. oscura/analyzers/digital/timing.py +260 -115
  7. oscura/analyzers/digital/timing_numba.py +334 -0
  8. oscura/analyzers/entropy.py +605 -0
  9. oscura/analyzers/eye/diagram.py +176 -109
  10. oscura/analyzers/eye/metrics.py +5 -5
  11. oscura/analyzers/jitter/__init__.py +6 -4
  12. oscura/analyzers/jitter/ber.py +52 -52
  13. oscura/analyzers/jitter/classification.py +156 -0
  14. oscura/analyzers/jitter/decomposition.py +163 -113
  15. oscura/analyzers/jitter/spectrum.py +80 -64
  16. oscura/analyzers/ml/__init__.py +39 -0
  17. oscura/analyzers/ml/features.py +600 -0
  18. oscura/analyzers/ml/signal_classifier.py +604 -0
  19. oscura/analyzers/packet/daq.py +246 -158
  20. oscura/analyzers/packet/parser.py +12 -1
  21. oscura/analyzers/packet/payload.py +50 -2110
  22. oscura/analyzers/packet/payload_analysis.py +361 -181
  23. oscura/analyzers/packet/payload_patterns.py +133 -70
  24. oscura/analyzers/packet/stream.py +84 -23
  25. oscura/analyzers/patterns/__init__.py +26 -5
  26. oscura/analyzers/patterns/anomaly_detection.py +908 -0
  27. oscura/analyzers/patterns/clustering.py +169 -108
  28. oscura/analyzers/patterns/clustering_optimized.py +227 -0
  29. oscura/analyzers/patterns/discovery.py +1 -1
  30. oscura/analyzers/patterns/matching.py +581 -197
  31. oscura/analyzers/patterns/pattern_mining.py +778 -0
  32. oscura/analyzers/patterns/periodic.py +121 -38
  33. oscura/analyzers/patterns/sequences.py +175 -78
  34. oscura/analyzers/power/conduction.py +1 -1
  35. oscura/analyzers/power/soa.py +6 -6
  36. oscura/analyzers/power/switching.py +250 -110
  37. oscura/analyzers/protocol/__init__.py +17 -1
  38. oscura/analyzers/protocols/base.py +6 -6
  39. oscura/analyzers/protocols/ble/__init__.py +38 -0
  40. oscura/analyzers/protocols/ble/analyzer.py +809 -0
  41. oscura/analyzers/protocols/ble/uuids.py +288 -0
  42. oscura/analyzers/protocols/can.py +257 -127
  43. oscura/analyzers/protocols/can_fd.py +107 -80
  44. oscura/analyzers/protocols/flexray.py +139 -80
  45. oscura/analyzers/protocols/hdlc.py +93 -58
  46. oscura/analyzers/protocols/i2c.py +247 -106
  47. oscura/analyzers/protocols/i2s.py +138 -86
  48. oscura/analyzers/protocols/industrial/__init__.py +40 -0
  49. oscura/analyzers/protocols/industrial/bacnet/__init__.py +33 -0
  50. oscura/analyzers/protocols/industrial/bacnet/analyzer.py +708 -0
  51. oscura/analyzers/protocols/industrial/bacnet/encoding.py +412 -0
  52. oscura/analyzers/protocols/industrial/bacnet/services.py +622 -0
  53. oscura/analyzers/protocols/industrial/ethercat/__init__.py +30 -0
  54. oscura/analyzers/protocols/industrial/ethercat/analyzer.py +474 -0
  55. oscura/analyzers/protocols/industrial/ethercat/mailbox.py +339 -0
  56. oscura/analyzers/protocols/industrial/ethercat/topology.py +166 -0
  57. oscura/analyzers/protocols/industrial/modbus/__init__.py +31 -0
  58. oscura/analyzers/protocols/industrial/modbus/analyzer.py +525 -0
  59. oscura/analyzers/protocols/industrial/modbus/crc.py +79 -0
  60. oscura/analyzers/protocols/industrial/modbus/functions.py +436 -0
  61. oscura/analyzers/protocols/industrial/opcua/__init__.py +21 -0
  62. oscura/analyzers/protocols/industrial/opcua/analyzer.py +552 -0
  63. oscura/analyzers/protocols/industrial/opcua/datatypes.py +446 -0
  64. oscura/analyzers/protocols/industrial/opcua/services.py +264 -0
  65. oscura/analyzers/protocols/industrial/profinet/__init__.py +23 -0
  66. oscura/analyzers/protocols/industrial/profinet/analyzer.py +441 -0
  67. oscura/analyzers/protocols/industrial/profinet/dcp.py +263 -0
  68. oscura/analyzers/protocols/industrial/profinet/ptcp.py +200 -0
  69. oscura/analyzers/protocols/jtag.py +180 -98
  70. oscura/analyzers/protocols/lin.py +219 -114
  71. oscura/analyzers/protocols/manchester.py +4 -4
  72. oscura/analyzers/protocols/onewire.py +253 -149
  73. oscura/analyzers/protocols/parallel_bus/__init__.py +20 -0
  74. oscura/analyzers/protocols/parallel_bus/centronics.py +92 -0
  75. oscura/analyzers/protocols/parallel_bus/gpib.py +137 -0
  76. oscura/analyzers/protocols/spi.py +192 -95
  77. oscura/analyzers/protocols/swd.py +321 -167
  78. oscura/analyzers/protocols/uart.py +267 -125
  79. oscura/analyzers/protocols/usb.py +235 -131
  80. oscura/analyzers/side_channel/power.py +17 -12
  81. oscura/analyzers/signal/__init__.py +15 -0
  82. oscura/analyzers/signal/timing_analysis.py +1086 -0
  83. oscura/analyzers/signal_integrity/__init__.py +4 -1
  84. oscura/analyzers/signal_integrity/sparams.py +2 -19
  85. oscura/analyzers/spectral/chunked.py +129 -60
  86. oscura/analyzers/spectral/chunked_fft.py +300 -94
  87. oscura/analyzers/spectral/chunked_wavelet.py +100 -80
  88. oscura/analyzers/statistical/checksum.py +376 -217
  89. oscura/analyzers/statistical/classification.py +229 -107
  90. oscura/analyzers/statistical/entropy.py +78 -53
  91. oscura/analyzers/statistics/correlation.py +407 -211
  92. oscura/analyzers/statistics/outliers.py +2 -2
  93. oscura/analyzers/statistics/streaming.py +30 -5
  94. oscura/analyzers/validation.py +216 -101
  95. oscura/analyzers/waveform/measurements.py +9 -0
  96. oscura/analyzers/waveform/measurements_with_uncertainty.py +31 -15
  97. oscura/analyzers/waveform/spectral.py +500 -228
  98. oscura/api/__init__.py +31 -5
  99. oscura/api/dsl/__init__.py +582 -0
  100. oscura/{dsl → api/dsl}/commands.py +43 -76
  101. oscura/{dsl → api/dsl}/interpreter.py +26 -51
  102. oscura/{dsl → api/dsl}/parser.py +107 -77
  103. oscura/{dsl → api/dsl}/repl.py +2 -2
  104. oscura/api/dsl.py +1 -1
  105. oscura/{integrations → api/integrations}/__init__.py +1 -1
  106. oscura/{integrations → api/integrations}/llm.py +201 -102
  107. oscura/api/operators.py +3 -3
  108. oscura/api/optimization.py +144 -30
  109. oscura/api/rest_server.py +921 -0
  110. oscura/api/server/__init__.py +17 -0
  111. oscura/api/server/dashboard.py +850 -0
  112. oscura/api/server/static/README.md +34 -0
  113. oscura/api/server/templates/base.html +181 -0
  114. oscura/api/server/templates/export.html +120 -0
  115. oscura/api/server/templates/home.html +284 -0
  116. oscura/api/server/templates/protocols.html +58 -0
  117. oscura/api/server/templates/reports.html +43 -0
  118. oscura/api/server/templates/session_detail.html +89 -0
  119. oscura/api/server/templates/sessions.html +83 -0
  120. oscura/api/server/templates/waveforms.html +73 -0
  121. oscura/automotive/__init__.py +8 -1
  122. oscura/automotive/can/__init__.py +10 -0
  123. oscura/automotive/can/checksum.py +3 -1
  124. oscura/automotive/can/dbc_generator.py +590 -0
  125. oscura/automotive/can/message_wrapper.py +121 -74
  126. oscura/automotive/can/patterns.py +98 -21
  127. oscura/automotive/can/session.py +292 -56
  128. oscura/automotive/can/state_machine.py +6 -3
  129. oscura/automotive/can/stimulus_response.py +97 -75
  130. oscura/automotive/dbc/__init__.py +10 -2
  131. oscura/automotive/dbc/generator.py +84 -56
  132. oscura/automotive/dbc/parser.py +6 -6
  133. oscura/automotive/dtc/data.json +17 -102
  134. oscura/automotive/dtc/database.py +2 -2
  135. oscura/automotive/flexray/__init__.py +31 -0
  136. oscura/automotive/flexray/analyzer.py +504 -0
  137. oscura/automotive/flexray/crc.py +185 -0
  138. oscura/automotive/flexray/fibex.py +449 -0
  139. oscura/automotive/j1939/__init__.py +45 -8
  140. oscura/automotive/j1939/analyzer.py +605 -0
  141. oscura/automotive/j1939/spns.py +326 -0
  142. oscura/automotive/j1939/transport.py +306 -0
  143. oscura/automotive/lin/__init__.py +47 -0
  144. oscura/automotive/lin/analyzer.py +612 -0
  145. oscura/automotive/loaders/blf.py +13 -2
  146. oscura/automotive/loaders/csv_can.py +143 -72
  147. oscura/automotive/loaders/dispatcher.py +50 -2
  148. oscura/automotive/loaders/mdf.py +86 -45
  149. oscura/automotive/loaders/pcap.py +111 -61
  150. oscura/automotive/uds/__init__.py +4 -0
  151. oscura/automotive/uds/analyzer.py +725 -0
  152. oscura/automotive/uds/decoder.py +140 -58
  153. oscura/automotive/uds/models.py +7 -1
  154. oscura/automotive/visualization.py +1 -1
  155. oscura/cli/analyze.py +348 -0
  156. oscura/cli/batch.py +142 -122
  157. oscura/cli/benchmark.py +275 -0
  158. oscura/cli/characterize.py +137 -82
  159. oscura/cli/compare.py +224 -131
  160. oscura/cli/completion.py +250 -0
  161. oscura/cli/config_cmd.py +361 -0
  162. oscura/cli/decode.py +164 -87
  163. oscura/cli/export.py +286 -0
  164. oscura/cli/main.py +115 -31
  165. oscura/{onboarding → cli/onboarding}/__init__.py +3 -3
  166. oscura/{onboarding → cli/onboarding}/help.py +80 -58
  167. oscura/{onboarding → cli/onboarding}/tutorials.py +97 -72
  168. oscura/{onboarding → cli/onboarding}/wizard.py +55 -36
  169. oscura/cli/progress.py +147 -0
  170. oscura/cli/shell.py +157 -135
  171. oscura/cli/validate_cmd.py +204 -0
  172. oscura/cli/visualize.py +158 -0
  173. oscura/convenience.py +125 -79
  174. oscura/core/__init__.py +4 -2
  175. oscura/core/backend_selector.py +3 -3
  176. oscura/core/cache.py +126 -15
  177. oscura/core/cancellation.py +1 -1
  178. oscura/{config → core/config}/__init__.py +20 -11
  179. oscura/{config → core/config}/defaults.py +1 -1
  180. oscura/{config → core/config}/loader.py +7 -5
  181. oscura/{config → core/config}/memory.py +5 -5
  182. oscura/{config → core/config}/migration.py +1 -1
  183. oscura/{config → core/config}/pipeline.py +99 -23
  184. oscura/{config → core/config}/preferences.py +1 -1
  185. oscura/{config → core/config}/protocol.py +3 -3
  186. oscura/{config → core/config}/schema.py +426 -272
  187. oscura/{config → core/config}/settings.py +1 -1
  188. oscura/{config → core/config}/thresholds.py +195 -153
  189. oscura/core/correlation.py +5 -6
  190. oscura/core/cross_domain.py +0 -2
  191. oscura/core/debug.py +9 -5
  192. oscura/{extensibility → core/extensibility}/docs.py +158 -70
  193. oscura/{extensibility → core/extensibility}/extensions.py +160 -76
  194. oscura/{extensibility → core/extensibility}/logging.py +1 -1
  195. oscura/{extensibility → core/extensibility}/measurements.py +1 -1
  196. oscura/{extensibility → core/extensibility}/plugins.py +1 -1
  197. oscura/{extensibility → core/extensibility}/templates.py +73 -3
  198. oscura/{extensibility → core/extensibility}/validation.py +1 -1
  199. oscura/core/gpu_backend.py +11 -7
  200. oscura/core/log_query.py +101 -11
  201. oscura/core/logging.py +126 -54
  202. oscura/core/logging_advanced.py +5 -5
  203. oscura/core/memory_limits.py +108 -70
  204. oscura/core/memory_monitor.py +2 -2
  205. oscura/core/memory_progress.py +7 -7
  206. oscura/core/memory_warnings.py +1 -1
  207. oscura/core/numba_backend.py +13 -13
  208. oscura/{plugins → core/plugins}/__init__.py +9 -9
  209. oscura/{plugins → core/plugins}/base.py +7 -7
  210. oscura/{plugins → core/plugins}/cli.py +3 -3
  211. oscura/{plugins → core/plugins}/discovery.py +186 -106
  212. oscura/{plugins → core/plugins}/lifecycle.py +1 -1
  213. oscura/{plugins → core/plugins}/manager.py +7 -7
  214. oscura/{plugins → core/plugins}/registry.py +3 -3
  215. oscura/{plugins → core/plugins}/versioning.py +1 -1
  216. oscura/core/progress.py +16 -1
  217. oscura/core/provenance.py +8 -2
  218. oscura/{schemas → core/schemas}/__init__.py +2 -2
  219. oscura/{schemas → core/schemas}/device_mapping.json +2 -8
  220. oscura/{schemas → core/schemas}/packet_format.json +4 -24
  221. oscura/{schemas → core/schemas}/protocol_definition.json +2 -12
  222. oscura/core/types.py +4 -0
  223. oscura/core/uncertainty.py +3 -3
  224. oscura/correlation/__init__.py +52 -0
  225. oscura/correlation/multi_protocol.py +811 -0
  226. oscura/discovery/auto_decoder.py +117 -35
  227. oscura/discovery/comparison.py +191 -86
  228. oscura/discovery/quality_validator.py +155 -68
  229. oscura/discovery/signal_detector.py +196 -79
  230. oscura/export/__init__.py +18 -8
  231. oscura/export/kaitai_struct.py +513 -0
  232. oscura/export/scapy_layer.py +801 -0
  233. oscura/export/wireshark/generator.py +1 -1
  234. oscura/export/wireshark/templates/dissector.lua.j2 +2 -2
  235. oscura/export/wireshark_dissector.py +746 -0
  236. oscura/guidance/wizard.py +207 -111
  237. oscura/hardware/__init__.py +19 -0
  238. oscura/{acquisition → hardware/acquisition}/__init__.py +4 -4
  239. oscura/{acquisition → hardware/acquisition}/file.py +2 -2
  240. oscura/{acquisition → hardware/acquisition}/hardware.py +7 -7
  241. oscura/{acquisition → hardware/acquisition}/saleae.py +15 -12
  242. oscura/{acquisition → hardware/acquisition}/socketcan.py +1 -1
  243. oscura/{acquisition → hardware/acquisition}/streaming.py +2 -2
  244. oscura/{acquisition → hardware/acquisition}/synthetic.py +3 -3
  245. oscura/{acquisition → hardware/acquisition}/visa.py +33 -11
  246. oscura/hardware/firmware/__init__.py +29 -0
  247. oscura/hardware/firmware/pattern_recognition.py +874 -0
  248. oscura/hardware/hal_detector.py +736 -0
  249. oscura/hardware/security/__init__.py +37 -0
  250. oscura/hardware/security/side_channel_detector.py +1126 -0
  251. oscura/inference/__init__.py +4 -0
  252. oscura/inference/active_learning/observation_table.py +4 -1
  253. oscura/inference/alignment.py +216 -123
  254. oscura/inference/bayesian.py +113 -33
  255. oscura/inference/crc_reverse.py +101 -55
  256. oscura/inference/logic.py +6 -2
  257. oscura/inference/message_format.py +342 -183
  258. oscura/inference/protocol.py +95 -44
  259. oscura/inference/protocol_dsl.py +180 -82
  260. oscura/inference/signal_intelligence.py +1439 -706
  261. oscura/inference/spectral.py +99 -57
  262. oscura/inference/state_machine.py +810 -158
  263. oscura/inference/stream.py +270 -110
  264. oscura/iot/__init__.py +34 -0
  265. oscura/iot/coap/__init__.py +32 -0
  266. oscura/iot/coap/analyzer.py +668 -0
  267. oscura/iot/coap/options.py +212 -0
  268. oscura/iot/lorawan/__init__.py +21 -0
  269. oscura/iot/lorawan/crypto.py +206 -0
  270. oscura/iot/lorawan/decoder.py +801 -0
  271. oscura/iot/lorawan/mac_commands.py +341 -0
  272. oscura/iot/mqtt/__init__.py +27 -0
  273. oscura/iot/mqtt/analyzer.py +999 -0
  274. oscura/iot/mqtt/properties.py +315 -0
  275. oscura/iot/zigbee/__init__.py +31 -0
  276. oscura/iot/zigbee/analyzer.py +615 -0
  277. oscura/iot/zigbee/security.py +153 -0
  278. oscura/iot/zigbee/zcl.py +349 -0
  279. oscura/jupyter/display.py +125 -45
  280. oscura/{exploratory → jupyter/exploratory}/__init__.py +8 -8
  281. oscura/{exploratory → jupyter/exploratory}/error_recovery.py +298 -141
  282. oscura/jupyter/exploratory/fuzzy.py +746 -0
  283. oscura/{exploratory → jupyter/exploratory}/fuzzy_advanced.py +258 -100
  284. oscura/{exploratory → jupyter/exploratory}/legacy.py +464 -242
  285. oscura/{exploratory → jupyter/exploratory}/parse.py +167 -145
  286. oscura/{exploratory → jupyter/exploratory}/recovery.py +119 -87
  287. oscura/jupyter/exploratory/sync.py +612 -0
  288. oscura/{exploratory → jupyter/exploratory}/unknown.py +299 -176
  289. oscura/jupyter/magic.py +4 -4
  290. oscura/{ui → jupyter/ui}/__init__.py +2 -2
  291. oscura/{ui → jupyter/ui}/formatters.py +3 -3
  292. oscura/{ui → jupyter/ui}/progressive_display.py +153 -82
  293. oscura/loaders/__init__.py +183 -67
  294. oscura/loaders/binary.py +88 -1
  295. oscura/loaders/chipwhisperer.py +153 -137
  296. oscura/loaders/configurable.py +208 -86
  297. oscura/loaders/csv_loader.py +458 -215
  298. oscura/loaders/hdf5_loader.py +278 -119
  299. oscura/loaders/lazy.py +87 -54
  300. oscura/loaders/mmap_loader.py +1 -1
  301. oscura/loaders/numpy_loader.py +253 -116
  302. oscura/loaders/pcap.py +226 -151
  303. oscura/loaders/rigol.py +110 -49
  304. oscura/loaders/sigrok.py +201 -78
  305. oscura/loaders/tdms.py +81 -58
  306. oscura/loaders/tektronix.py +291 -174
  307. oscura/loaders/touchstone.py +182 -87
  308. oscura/loaders/tss.py +456 -0
  309. oscura/loaders/vcd.py +215 -117
  310. oscura/loaders/wav.py +155 -68
  311. oscura/reporting/__init__.py +9 -0
  312. oscura/reporting/analyze.py +352 -146
  313. oscura/reporting/argument_preparer.py +69 -14
  314. oscura/reporting/auto_report.py +97 -61
  315. oscura/reporting/batch.py +131 -58
  316. oscura/reporting/chart_selection.py +57 -45
  317. oscura/reporting/comparison.py +63 -17
  318. oscura/reporting/content/executive.py +76 -24
  319. oscura/reporting/core_formats/multi_format.py +11 -8
  320. oscura/reporting/engine.py +312 -158
  321. oscura/reporting/enhanced_reports.py +949 -0
  322. oscura/reporting/export.py +86 -43
  323. oscura/reporting/formatting/numbers.py +69 -42
  324. oscura/reporting/html.py +139 -58
  325. oscura/reporting/index.py +137 -65
  326. oscura/reporting/output.py +158 -67
  327. oscura/reporting/pdf.py +67 -102
  328. oscura/reporting/plots.py +191 -112
  329. oscura/reporting/sections.py +88 -47
  330. oscura/reporting/standards.py +104 -61
  331. oscura/reporting/summary_generator.py +75 -55
  332. oscura/reporting/tables.py +138 -54
  333. oscura/reporting/templates/enhanced/protocol_re.html +525 -0
  334. oscura/sessions/__init__.py +14 -23
  335. oscura/sessions/base.py +3 -3
  336. oscura/sessions/blackbox.py +106 -10
  337. oscura/sessions/generic.py +2 -2
  338. oscura/sessions/legacy.py +783 -0
  339. oscura/side_channel/__init__.py +63 -0
  340. oscura/side_channel/dpa.py +1025 -0
  341. oscura/utils/__init__.py +15 -1
  342. oscura/utils/bitwise.py +118 -0
  343. oscura/{builders → utils/builders}/__init__.py +1 -1
  344. oscura/{comparison → utils/comparison}/__init__.py +6 -6
  345. oscura/{comparison → utils/comparison}/compare.py +202 -101
  346. oscura/{comparison → utils/comparison}/golden.py +83 -63
  347. oscura/{comparison → utils/comparison}/limits.py +313 -89
  348. oscura/{comparison → utils/comparison}/mask.py +151 -45
  349. oscura/{comparison → utils/comparison}/trace_diff.py +1 -1
  350. oscura/{comparison → utils/comparison}/visualization.py +147 -89
  351. oscura/{component → utils/component}/__init__.py +3 -3
  352. oscura/{component → utils/component}/impedance.py +122 -58
  353. oscura/{component → utils/component}/reactive.py +165 -168
  354. oscura/{component → utils/component}/transmission_line.py +3 -3
  355. oscura/{filtering → utils/filtering}/__init__.py +6 -6
  356. oscura/{filtering → utils/filtering}/base.py +1 -1
  357. oscura/{filtering → utils/filtering}/convenience.py +2 -2
  358. oscura/{filtering → utils/filtering}/design.py +169 -93
  359. oscura/{filtering → utils/filtering}/filters.py +2 -2
  360. oscura/{filtering → utils/filtering}/introspection.py +2 -2
  361. oscura/utils/geometry.py +31 -0
  362. oscura/utils/imports.py +184 -0
  363. oscura/utils/lazy.py +1 -1
  364. oscura/{math → utils/math}/__init__.py +2 -2
  365. oscura/{math → utils/math}/arithmetic.py +114 -48
  366. oscura/{math → utils/math}/interpolation.py +139 -106
  367. oscura/utils/memory.py +129 -66
  368. oscura/utils/memory_advanced.py +92 -9
  369. oscura/utils/memory_extensions.py +10 -8
  370. oscura/{optimization → utils/optimization}/__init__.py +1 -1
  371. oscura/{optimization → utils/optimization}/search.py +2 -2
  372. oscura/utils/performance/__init__.py +58 -0
  373. oscura/utils/performance/caching.py +889 -0
  374. oscura/utils/performance/lsh_clustering.py +333 -0
  375. oscura/utils/performance/memory_optimizer.py +699 -0
  376. oscura/utils/performance/optimizations.py +675 -0
  377. oscura/utils/performance/parallel.py +654 -0
  378. oscura/utils/performance/profiling.py +661 -0
  379. oscura/{pipeline → utils/pipeline}/base.py +1 -1
  380. oscura/{pipeline → utils/pipeline}/composition.py +1 -1
  381. oscura/{pipeline → utils/pipeline}/parallel.py +3 -2
  382. oscura/{pipeline → utils/pipeline}/pipeline.py +1 -1
  383. oscura/{pipeline → utils/pipeline}/reverse_engineering.py +412 -221
  384. oscura/{search → utils/search}/__init__.py +3 -3
  385. oscura/{search → utils/search}/anomaly.py +188 -58
  386. oscura/utils/search/context.py +294 -0
  387. oscura/{search → utils/search}/pattern.py +138 -10
  388. oscura/utils/serial.py +51 -0
  389. oscura/utils/storage/__init__.py +61 -0
  390. oscura/utils/storage/database.py +1166 -0
  391. oscura/{streaming → utils/streaming}/chunked.py +302 -143
  392. oscura/{streaming → utils/streaming}/progressive.py +1 -1
  393. oscura/{streaming → utils/streaming}/realtime.py +3 -2
  394. oscura/{triggering → utils/triggering}/__init__.py +6 -6
  395. oscura/{triggering → utils/triggering}/base.py +6 -6
  396. oscura/{triggering → utils/triggering}/edge.py +2 -2
  397. oscura/{triggering → utils/triggering}/pattern.py +2 -2
  398. oscura/{triggering → utils/triggering}/pulse.py +115 -74
  399. oscura/{triggering → utils/triggering}/window.py +2 -2
  400. oscura/utils/validation.py +32 -0
  401. oscura/validation/__init__.py +121 -0
  402. oscura/{compliance → validation/compliance}/__init__.py +5 -5
  403. oscura/{compliance → validation/compliance}/advanced.py +5 -5
  404. oscura/{compliance → validation/compliance}/masks.py +1 -1
  405. oscura/{compliance → validation/compliance}/reporting.py +127 -53
  406. oscura/{compliance → validation/compliance}/testing.py +114 -52
  407. oscura/validation/compliance_tests.py +915 -0
  408. oscura/validation/fuzzer.py +990 -0
  409. oscura/validation/grammar_tests.py +596 -0
  410. oscura/validation/grammar_validator.py +904 -0
  411. oscura/validation/hil_testing.py +977 -0
  412. oscura/{quality → validation/quality}/__init__.py +4 -4
  413. oscura/{quality → validation/quality}/ensemble.py +251 -171
  414. oscura/{quality → validation/quality}/explainer.py +3 -3
  415. oscura/{quality → validation/quality}/scoring.py +1 -1
  416. oscura/{quality → validation/quality}/warnings.py +4 -4
  417. oscura/validation/regression_suite.py +808 -0
  418. oscura/validation/replay.py +788 -0
  419. oscura/{testing → validation/testing}/__init__.py +2 -2
  420. oscura/{testing → validation/testing}/synthetic.py +5 -5
  421. oscura/visualization/__init__.py +9 -0
  422. oscura/visualization/accessibility.py +1 -1
  423. oscura/visualization/annotations.py +64 -67
  424. oscura/visualization/colors.py +7 -7
  425. oscura/visualization/digital.py +180 -81
  426. oscura/visualization/eye.py +236 -85
  427. oscura/visualization/interactive.py +320 -143
  428. oscura/visualization/jitter.py +587 -247
  429. oscura/visualization/layout.py +169 -134
  430. oscura/visualization/optimization.py +103 -52
  431. oscura/visualization/palettes.py +1 -1
  432. oscura/visualization/power.py +427 -211
  433. oscura/visualization/power_extended.py +626 -297
  434. oscura/visualization/presets.py +2 -0
  435. oscura/visualization/protocols.py +495 -181
  436. oscura/visualization/render.py +79 -63
  437. oscura/visualization/reverse_engineering.py +171 -124
  438. oscura/visualization/signal_integrity.py +460 -279
  439. oscura/visualization/specialized.py +190 -100
  440. oscura/visualization/spectral.py +670 -255
  441. oscura/visualization/thumbnails.py +166 -137
  442. oscura/visualization/waveform.py +150 -63
  443. oscura/workflows/__init__.py +3 -0
  444. oscura/{batch → workflows/batch}/__init__.py +5 -5
  445. oscura/{batch → workflows/batch}/advanced.py +150 -75
  446. oscura/workflows/batch/aggregate.py +531 -0
  447. oscura/workflows/batch/analyze.py +236 -0
  448. oscura/{batch → workflows/batch}/logging.py +2 -2
  449. oscura/{batch → workflows/batch}/metrics.py +1 -1
  450. oscura/workflows/complete_re.py +1144 -0
  451. oscura/workflows/compliance.py +44 -54
  452. oscura/workflows/digital.py +197 -51
  453. oscura/workflows/legacy/__init__.py +12 -0
  454. oscura/{workflow → workflows/legacy}/dag.py +4 -1
  455. oscura/workflows/multi_trace.py +9 -9
  456. oscura/workflows/power.py +42 -62
  457. oscura/workflows/protocol.py +82 -49
  458. oscura/workflows/reverse_engineering.py +351 -150
  459. oscura/workflows/signal_integrity.py +157 -82
  460. oscura-0.7.0.dist-info/METADATA +661 -0
  461. oscura-0.7.0.dist-info/RECORD +591 -0
  462. oscura/batch/aggregate.py +0 -300
  463. oscura/batch/analyze.py +0 -139
  464. oscura/dsl/__init__.py +0 -73
  465. oscura/exceptions.py +0 -59
  466. oscura/exploratory/fuzzy.py +0 -513
  467. oscura/exploratory/sync.py +0 -384
  468. oscura/exporters/__init__.py +0 -94
  469. oscura/exporters/csv.py +0 -303
  470. oscura/exporters/exporters.py +0 -44
  471. oscura/exporters/hdf5.py +0 -217
  472. oscura/exporters/html_export.py +0 -701
  473. oscura/exporters/json_export.py +0 -291
  474. oscura/exporters/markdown_export.py +0 -367
  475. oscura/exporters/matlab_export.py +0 -354
  476. oscura/exporters/npz_export.py +0 -219
  477. oscura/exporters/spice_export.py +0 -210
  478. oscura/search/context.py +0 -149
  479. oscura/session/__init__.py +0 -34
  480. oscura/session/annotations.py +0 -289
  481. oscura/session/history.py +0 -313
  482. oscura/session/session.py +0 -520
  483. oscura/workflow/__init__.py +0 -13
  484. oscura-0.5.1.dist-info/METADATA +0 -583
  485. oscura-0.5.1.dist-info/RECORD +0 -481
  486. /oscura/core/{config.py → config/legacy.py} +0 -0
  487. /oscura/{extensibility → core/extensibility}/__init__.py +0 -0
  488. /oscura/{extensibility → core/extensibility}/registry.py +0 -0
  489. /oscura/{plugins → core/plugins}/isolation.py +0 -0
  490. /oscura/{schemas → core/schemas}/bus_configuration.json +0 -0
  491. /oscura/{builders → utils/builders}/signal_builder.py +0 -0
  492. /oscura/{optimization → utils/optimization}/parallel.py +0 -0
  493. /oscura/{pipeline → utils/pipeline}/__init__.py +0 -0
  494. /oscura/{streaming → utils/streaming}/__init__.py +0 -0
  495. {oscura-0.5.1.dist-info → oscura-0.7.0.dist-info}/WHEEL +0 -0
  496. {oscura-0.5.1.dist-info → oscura-0.7.0.dist-info}/entry_points.txt +0 -0
  497. {oscura-0.5.1.dist-info → oscura-0.7.0.dist-info}/licenses/LICENSE +0 -0
@@ -5,7 +5,7 @@ including API reference, usage examples, and metadata extraction from docstrings
5
5
 
6
6
 
7
7
  Example:
8
- >>> from oscura.extensibility.docs import generate_extension_docs
8
+ >>> from oscura.core.extensibility.docs import generate_extension_docs
9
9
  >>> from pathlib import Path
10
10
  >>>
11
11
  >>> # Generate documentation for an extension
@@ -561,39 +561,65 @@ def _process_section(
561
561
  include_examples: Extract examples
562
562
  """
563
563
  if section in ["args", "arguments", "parameters"]:
564
- # Parse parameters
565
- for line in content:
566
- line = line.strip()
567
- if ":" in line:
568
- parts = line.split(":", 1)
569
- param_name = parts[0].strip()
570
- param_desc = parts[1].strip()
571
- func_doc.parameters.append((param_name, param_desc))
572
-
564
+ _process_parameters_section(func_doc, content)
573
565
  elif section in ["returns", "return"]:
574
- func_doc.returns = "\n".join(content).strip()
575
-
566
+ _process_returns_section(func_doc, content)
576
567
  elif section in ["example", "examples"] and include_examples:
577
- # Extract code blocks
578
- in_code = False
579
- code_lines = []
580
-
581
- for line in content:
582
- if ">>>" in line or "..." in line:
583
- in_code = True
584
- code_lines.append(line.strip())
585
- elif in_code:
586
- if line.strip() and not line.strip().startswith("#"):
587
- if not (">>>" in line or "..." in line):
588
- in_code = False
589
- if code_lines:
590
- func_doc.examples.append("\n".join(code_lines))
591
- code_lines = []
592
- else:
593
- code_lines.append(line.strip())
594
-
595
- if code_lines:
596
- func_doc.examples.append("\n".join(code_lines))
568
+ _process_examples_section(func_doc, content)
569
+
570
+
571
+ def _process_parameters_section(func_doc: FunctionDoc, content: list[str]) -> None:
572
+ """Process parameters section.
573
+
574
+ Args:
575
+ func_doc: FunctionDoc to populate.
576
+ content: Section content lines.
577
+ """
578
+ for line in content:
579
+ line = line.strip()
580
+ if ":" in line:
581
+ parts = line.split(":", 1)
582
+ param_name = parts[0].strip()
583
+ param_desc = parts[1].strip()
584
+ func_doc.parameters.append((param_name, param_desc))
585
+
586
+
587
+ def _process_returns_section(func_doc: FunctionDoc, content: list[str]) -> None:
588
+ """Process returns section.
589
+
590
+ Args:
591
+ func_doc: FunctionDoc to populate.
592
+ content: Section content lines.
593
+ """
594
+ func_doc.returns = "\n".join(content).strip()
595
+
596
+
597
+ def _process_examples_section(func_doc: FunctionDoc, content: list[str]) -> None:
598
+ """Process examples section.
599
+
600
+ Args:
601
+ func_doc: FunctionDoc to populate.
602
+ content: Section content lines.
603
+ """
604
+ in_code = False
605
+ code_lines = []
606
+
607
+ for line in content:
608
+ if ">>>" in line or "..." in line:
609
+ in_code = True
610
+ code_lines.append(line.strip())
611
+ elif in_code:
612
+ if line.strip() and not line.strip().startswith("#"):
613
+ if not (">>>" in line or "..." in line):
614
+ in_code = False
615
+ if code_lines:
616
+ func_doc.examples.append("\n".join(code_lines))
617
+ code_lines = []
618
+ else:
619
+ code_lines.append(line.strip())
620
+
621
+ if code_lines:
622
+ func_doc.examples.append("\n".join(code_lines))
597
623
 
598
624
 
599
625
  def _get_name_from_ast(node: ast.expr) -> str:
@@ -622,13 +648,30 @@ def _generate_markdown(docs: ExtensionDocs) -> str:
622
648
  Returns:
623
649
  Markdown string
624
650
  """
625
- lines = []
651
+ lines: list[str] = []
652
+
653
+ # Title and metadata
654
+ _add_markdown_header(lines, docs)
626
655
 
627
- # Title
656
+ # Dependencies
657
+ _add_markdown_dependencies(lines, docs)
658
+
659
+ # Modules
660
+ _add_markdown_modules(lines, docs)
661
+
662
+ return "\n".join(lines)
663
+
664
+
665
+ def _add_markdown_header(lines: list[str], docs: ExtensionDocs) -> None:
666
+ """Add header section to markdown.
667
+
668
+ Args:
669
+ lines: List to append lines to.
670
+ docs: ExtensionDocs to render.
671
+ """
628
672
  lines.append(f"# {docs.name}")
629
673
  lines.append("")
630
674
 
631
- # Metadata
632
675
  if docs.version:
633
676
  lines.append(f"**Version:** {docs.version}")
634
677
  lines.append("")
@@ -639,7 +682,14 @@ def _generate_markdown(docs: ExtensionDocs) -> str:
639
682
  lines.append(docs.description)
640
683
  lines.append("")
641
684
 
642
- # Dependencies
685
+
686
+ def _add_markdown_dependencies(lines: list[str], docs: ExtensionDocs) -> None:
687
+ """Add dependencies section to markdown.
688
+
689
+ Args:
690
+ lines: List to append lines to.
691
+ docs: ExtensionDocs to render.
692
+ """
643
693
  if "dependencies" in docs.metadata:
644
694
  lines.append("## Dependencies")
645
695
  lines.append("")
@@ -647,7 +697,14 @@ def _generate_markdown(docs: ExtensionDocs) -> str:
647
697
  lines.append(f"- {dep}")
648
698
  lines.append("")
649
699
 
650
- # Modules
700
+
701
+ def _add_markdown_modules(lines: list[str], docs: ExtensionDocs) -> None:
702
+ """Add modules section to markdown.
703
+
704
+ Args:
705
+ lines: List to append lines to.
706
+ docs: ExtensionDocs to render.
707
+ """
651
708
  for module in docs.modules:
652
709
  lines.append(f"## Module: {module.name}")
653
710
  lines.append("")
@@ -656,43 +713,74 @@ def _generate_markdown(docs: ExtensionDocs) -> str:
656
713
  lines.append("")
657
714
 
658
715
  # Classes
659
- for cls in module.classes:
660
- lines.append(f"### Class: {cls.name}")
661
- lines.append("")
662
- if cls.docstring:
663
- lines.append(cls.docstring)
664
- lines.append("")
665
-
666
- # Methods
667
- if cls.methods:
668
- lines.append("#### Methods")
669
- lines.append("")
670
- for method in cls.methods:
671
- lines.append(f"##### {method.name}")
672
- lines.append("")
673
- if method.signature:
674
- lines.append("```python")
675
- lines.append(method.signature)
676
- lines.append("```")
677
- lines.append("")
678
- if method.docstring:
679
- lines.append(method.docstring)
680
- lines.append("")
716
+ _add_markdown_classes(lines, module)
681
717
 
682
718
  # Functions
683
- for func in module.functions:
684
- lines.append(f"### Function: {func.name}")
719
+ _add_markdown_functions(lines, module)
720
+
721
+
722
+ def _add_markdown_classes(lines: list[str], module: ModuleDoc) -> None:
723
+ """Add classes section to markdown.
724
+
725
+ Args:
726
+ lines: List to append lines to.
727
+ module: ModuleDoc to render.
728
+ """
729
+ for cls in module.classes:
730
+ lines.append(f"### Class: {cls.name}")
731
+ lines.append("")
732
+ if cls.docstring:
733
+ lines.append(cls.docstring)
685
734
  lines.append("")
686
- if func.signature:
687
- lines.append("```python")
688
- lines.append(func.signature)
689
- lines.append("```")
690
- lines.append("")
691
- if func.docstring:
692
- lines.append(func.docstring)
693
- lines.append("")
694
735
 
695
- return "\n".join(lines)
736
+ # Methods
737
+ if cls.methods:
738
+ lines.append("#### Methods")
739
+ lines.append("")
740
+ _add_markdown_methods(lines, cls.methods)
741
+
742
+
743
+ def _add_markdown_methods(lines: list[str], methods: list[FunctionDoc]) -> None:
744
+ """Add methods section to markdown.
745
+
746
+ Args:
747
+ lines: List to append lines to.
748
+ methods: List of method documentation.
749
+ """
750
+ for method in methods:
751
+ lines.append(f"##### {method.name}")
752
+ lines.append("")
753
+ _add_markdown_function_details(lines, method)
754
+
755
+
756
+ def _add_markdown_functions(lines: list[str], module: ModuleDoc) -> None:
757
+ """Add functions section to markdown.
758
+
759
+ Args:
760
+ lines: List to append lines to.
761
+ module: ModuleDoc to render.
762
+ """
763
+ for func in module.functions:
764
+ lines.append(f"### Function: {func.name}")
765
+ lines.append("")
766
+ _add_markdown_function_details(lines, func)
767
+
768
+
769
+ def _add_markdown_function_details(lines: list[str], func: FunctionDoc) -> None:
770
+ """Add function/method details to markdown.
771
+
772
+ Args:
773
+ lines: List to append lines to.
774
+ func: FunctionDoc to render.
775
+ """
776
+ if func.signature:
777
+ lines.append("```python")
778
+ lines.append(func.signature)
779
+ lines.append("```")
780
+ lines.append("")
781
+ if func.docstring:
782
+ lines.append(func.docstring)
783
+ lines.append("")
696
784
 
697
785
 
698
786
  def _generate_html(docs: ExtensionDocs) -> str:
@@ -250,7 +250,7 @@ class ExtensionPointRegistry:
250
250
  cls._instance._hook_error_policy = HookErrorPolicy.CONTINUE
251
251
  cls._instance._log_hook_errors = True
252
252
  cls._instance._initialized = False # type: ignore[has-type]
253
- cls._instance._registration_counter = 0 # type: ignore[misc, attr-defined]
253
+ cls._instance._registration_counter: int = 0 # type: ignore[misc, attr-defined]
254
254
  return cls._instance
255
255
 
256
256
  def initialize(self) -> None:
@@ -530,99 +530,183 @@ class ExtensionPointRegistry:
530
530
  if name:
531
531
  return self.get_algorithm(category, name)
532
532
 
533
- # Auto-select based on criteria
533
+ # Get and filter candidates
534
534
  candidates = list(self._algorithms[category].values()) # type: ignore[attr-defined]
535
-
536
535
  if not candidates:
537
536
  raise KeyError(f"No algorithms registered in category '{category}'")
538
537
 
539
- # Filter by required capabilities (EXT-003)
540
- if required_capabilities:
541
- filtered = []
542
- for algo in candidates:
543
- if all(algo.can(cap) for cap in required_capabilities):
544
- filtered.append(algo)
545
- candidates = filtered
538
+ candidates = self._filter_by_capabilities(candidates, required_capabilities, category)
539
+ candidates = self._filter_by_constraints(candidates, constraints, category)
546
540
 
547
- if not candidates:
541
+ # Select best match
542
+ sort_key = self._get_sort_key(optimize_for)
543
+ candidates.sort(key=sort_key)
544
+ return candidates[0]
545
+
546
+ def _filter_by_capabilities(
547
+ self,
548
+ candidates: list[RegisteredAlgorithm],
549
+ required_capabilities: list[str] | None,
550
+ category: str,
551
+ ) -> list[RegisteredAlgorithm]:
552
+ """Filter algorithms by required capabilities.
553
+
554
+ Args:
555
+ candidates: List of candidate algorithms.
556
+ required_capabilities: Required capabilities.
557
+ category: Category name for error messages.
558
+
559
+ Returns:
560
+ Filtered list of algorithms.
561
+
562
+ Raises:
563
+ KeyError: If no algorithms match capabilities.
564
+ """
565
+ if not required_capabilities:
566
+ return candidates
567
+
568
+ filtered = [
569
+ algo for algo in candidates if all(algo.can(cap) for cap in required_capabilities)
570
+ ]
571
+
572
+ if not filtered:
548
573
  raise KeyError(f"No algorithms match required capabilities in category '{category}'")
549
574
 
550
- # Apply constraints
551
- if constraints:
552
- filtered = []
553
- for algo in candidates:
554
- match = True
555
- for key, value in constraints.items():
556
- if key.startswith("performance."):
557
- perf_key = key.split(".", 1)[1]
558
- if algo.performance.get(perf_key) != value:
559
- match = False
560
- break
561
- elif key.startswith("capabilities."):
562
- cap_key = key.split(".", 1)[1]
563
- if algo.capabilities.get(cap_key) != value:
564
- match = False
565
- break
566
- elif key == "supports":
567
- if isinstance(value, list):
568
- if not any(s in algo.supports for s in value):
569
- match = False
570
- break
571
- elif value not in algo.supports:
572
- match = False
573
- break
574
- elif key == "memory_usage":
575
- if algo.memory_usage != value:
576
- match = False
577
- break
578
- if match:
579
- filtered.append(algo)
580
- candidates = filtered
575
+ return filtered
581
576
 
582
- if not candidates:
577
+ def _filter_by_constraints(
578
+ self,
579
+ candidates: list[RegisteredAlgorithm],
580
+ constraints: dict[str, Any] | None,
581
+ category: str,
582
+ ) -> list[RegisteredAlgorithm]:
583
+ """Filter algorithms by constraints.
584
+
585
+ Args:
586
+ candidates: List of candidate algorithms.
587
+ constraints: Constraint dictionary.
588
+ category: Category name for error messages.
589
+
590
+ Returns:
591
+ Filtered list of algorithms.
592
+
593
+ Raises:
594
+ KeyError: If no algorithms match constraints.
595
+ """
596
+ if not constraints:
597
+ return candidates
598
+
599
+ filtered = [algo for algo in candidates if self._matches_constraints(algo, constraints)]
600
+
601
+ if not filtered:
583
602
  raise KeyError(f"No algorithms match constraints in category '{category}'")
584
603
 
585
- # Sort by optimization criteria
604
+ return filtered
605
+
606
+ def _matches_constraints(
607
+ self,
608
+ algo: RegisteredAlgorithm,
609
+ constraints: dict[str, Any],
610
+ ) -> bool:
611
+ """Check if algorithm matches all constraints.
612
+
613
+ Args:
614
+ algo: Algorithm to check.
615
+ constraints: Constraint dictionary.
616
+
617
+ Returns:
618
+ True if all constraints match.
619
+ """
620
+ for key, value in constraints.items():
621
+ if not self._check_single_constraint(algo, key, value):
622
+ return False
623
+ return True
624
+
625
+ def _check_single_constraint(
626
+ self,
627
+ algo: RegisteredAlgorithm,
628
+ key: str,
629
+ value: Any,
630
+ ) -> bool:
631
+ """Check if algorithm matches a single constraint.
632
+
633
+ Args:
634
+ algo: Algorithm to check.
635
+ key: Constraint key.
636
+ value: Expected value.
637
+
638
+ Returns:
639
+ True if constraint matches.
640
+ """
641
+ if key.startswith("performance."):
642
+ return self._check_performance_constraint(algo, key, value)
643
+ elif key.startswith("capabilities."):
644
+ return self._check_capabilities_constraint(algo, key, value)
645
+ elif key == "supports":
646
+ return self._check_supports_constraint(algo, value)
647
+ elif key == "memory_usage":
648
+ memory_check: bool = bool(algo.memory_usage == value)
649
+ return memory_check
650
+ return True
651
+
652
+ def _check_performance_constraint(
653
+ self, algo: RegisteredAlgorithm, key: str, value: Any
654
+ ) -> bool:
655
+ """Check performance constraint."""
656
+ perf_key = key.split(".", 1)[1]
657
+ result: bool = bool(algo.performance.get(perf_key) == value)
658
+ return result
659
+
660
+ def _check_capabilities_constraint(
661
+ self, algo: RegisteredAlgorithm, key: str, value: Any
662
+ ) -> bool:
663
+ """Check capabilities constraint."""
664
+ cap_key = key.split(".", 1)[1]
665
+ result: bool = bool(algo.capabilities.get(cap_key) == value)
666
+ return result
667
+
668
+ def _check_supports_constraint(self, algo: RegisteredAlgorithm, value: Any) -> bool:
669
+ """Check supports constraint."""
670
+ if isinstance(value, list):
671
+ result: bool = bool(any(s in algo.supports for s in value))
672
+ return result
673
+ else:
674
+ in_supports: bool = bool(value in algo.supports)
675
+ return in_supports
676
+
677
+ def _get_sort_key(self, optimize_for: str) -> Any:
678
+ """Get sort key function for optimization criterion.
679
+
680
+ Args:
681
+ optimize_for: Optimization target.
682
+
683
+ Returns:
684
+ Sort key function.
685
+ """
586
686
  if optimize_for == "speed":
587
687
 
588
- def sort_key(a): # type: ignore[no-untyped-def]
589
- return (
590
- 0
591
- if a.performance.get("speed") == "fast"
592
- else 1
593
- if a.performance.get("speed") == "medium"
594
- else 2,
595
- -a.priority,
596
- )
688
+ def sort_key(a: RegisteredAlgorithm) -> tuple[int, int]:
689
+ speed = a.performance.get("speed")
690
+ rank = 0 if speed == "fast" else 1 if speed == "medium" else 2
691
+ return (rank, -a.priority)
597
692
  elif optimize_for == "accuracy":
598
693
 
599
- def sort_key(a): # type: ignore[no-untyped-def]
600
- return (
601
- 0
602
- if a.performance.get("accuracy") == "high"
603
- else 1
604
- if a.performance.get("accuracy") == "medium"
605
- else 2,
606
- -a.priority,
607
- )
694
+ def sort_key(a: RegisteredAlgorithm) -> tuple[int, int]:
695
+ acc = a.performance.get("accuracy")
696
+ rank = 0 if acc == "high" else 1 if acc == "medium" else 2
697
+ return (rank, -a.priority)
608
698
  elif optimize_for == "memory":
609
699
 
610
- def sort_key(a): # type: ignore[no-untyped-def]
611
- return (
612
- 0
613
- if a.performance.get("memory") == "low"
614
- else 1
615
- if a.performance.get("memory") == "medium"
616
- else 2,
617
- -a.priority,
618
- )
700
+ def sort_key(a: RegisteredAlgorithm) -> tuple[int, int]:
701
+ mem = a.performance.get("memory")
702
+ rank = 0 if mem == "low" else 1 if mem == "medium" else 2
703
+ return (rank, -a.priority)
619
704
  else:
620
- # Default to priority only
621
- def sort_key(a): # type: ignore[no-untyped-def]
622
- return -a.priority
623
705
 
624
- candidates.sort(key=sort_key)
625
- return candidates[0] # type: ignore[no-any-return]
706
+ def sort_key(a: RegisteredAlgorithm) -> tuple[int, int]:
707
+ return (-a.priority, 0)
708
+
709
+ return sort_key
626
710
 
627
711
  def list_algorithms(
628
712
  self,
@@ -5,7 +5,7 @@ and per-plugin configuration for proper log management.
5
5
 
6
6
 
7
7
  Example:
8
- >>> from oscura.extensibility.logging import get_plugin_logger
8
+ >>> from oscura.core.extensibility.logging import get_plugin_logger
9
9
  >>> logger = get_plugin_logger("my_plugin")
10
10
  >>> logger.info("Plugin initialized")
11
11
 
@@ -15,7 +15,7 @@ from .registry import AlgorithmRegistry
15
15
  if TYPE_CHECKING:
16
16
  from collections.abc import Callable
17
17
 
18
- from ..core.types import WaveformTrace
18
+ from oscura.core.types import WaveformTrace
19
19
 
20
20
 
21
21
  @dataclass
@@ -117,7 +117,7 @@ class PluginManager:
117
117
  """
118
118
 
119
119
  # Standard entry point groups
120
- ENTRY_POINT_GROUPS = [ # noqa: RUF012
120
+ ENTRY_POINT_GROUPS = [
121
121
  "oscura.decoders",
122
122
  "oscura.measurements",
123
123
  "oscura.loaders",
@@ -312,7 +312,41 @@ def _write_readme(template: PluginTemplate) -> None:
312
312
  class_name = _get_class_name(template)
313
313
  entry_point_group = _get_entry_point_group(template.plugin_type)
314
314
 
315
- content = textwrap.dedent(f"""\
315
+ content = _generate_readme_content(template, class_name, entry_point_group)
316
+ (template.output_dir / "README.md").write_text(content)
317
+
318
+
319
+ def _generate_readme_content(
320
+ template: PluginTemplate, class_name: str, entry_point_group: str
321
+ ) -> str:
322
+ """Generate README.md content.
323
+
324
+ Args:
325
+ template: Plugin template configuration.
326
+ class_name: PascalCase class name.
327
+ entry_point_group: Entry point group name.
328
+
329
+ Returns:
330
+ README content as string.
331
+ """
332
+ header = _generate_readme_header(template, class_name)
333
+ usage = _generate_readme_usage(template, class_name)
334
+ dev = _generate_readme_development(template)
335
+ metadata = _generate_readme_metadata(template, entry_point_group)
336
+ return f"{header}\n{usage}\n{dev}\n{metadata}"
337
+
338
+
339
+ def _generate_readme_header(template: PluginTemplate, class_name: str) -> str:
340
+ """Generate README header and installation section.
341
+
342
+ Args:
343
+ template: Plugin template configuration.
344
+ class_name: Class name.
345
+
346
+ Returns:
347
+ Header section content.
348
+ """
349
+ return textwrap.dedent(f"""\
316
350
  # {class_name}
317
351
 
318
352
  {template.description}
@@ -325,7 +359,20 @@ def _write_readme(template: PluginTemplate) -> None:
325
359
  cd {template.output_dir.name}
326
360
  pip install -e .
327
361
  ```
362
+ """)
363
+
364
+
365
+ def _generate_readme_usage(template: PluginTemplate, class_name: str) -> str:
366
+ """Generate usage section.
328
367
 
368
+ Args:
369
+ template: Plugin template configuration.
370
+ class_name: Class name.
371
+
372
+ Returns:
373
+ Usage section content.
374
+ """
375
+ return textwrap.dedent(f"""\
329
376
  ## Usage
330
377
 
331
378
  The plugin integrates automatically with Oscura via entry points:
@@ -359,7 +406,19 @@ def _write_readme(template: PluginTemplate) -> None:
359
406
  # Show plugin info
360
407
  oscura plugin info {template.name}
361
408
  ```
409
+ """)
410
+
411
+
412
+ def _generate_readme_development(template: PluginTemplate) -> str:
413
+ """Generate development section.
362
414
 
415
+ Args:
416
+ template: Plugin template configuration.
417
+
418
+ Returns:
419
+ Development section content.
420
+ """
421
+ return textwrap.dedent(f"""\
363
422
  ## Development
364
423
 
365
424
  ### Running Tests
@@ -380,7 +439,20 @@ def _write_readme(template: PluginTemplate) -> None:
380
439
  # Type checking
381
440
  mypy {template.name}.py
382
441
  ```
442
+ """)
443
+
444
+
445
+ def _generate_readme_metadata(template: PluginTemplate, entry_point_group: str) -> str:
446
+ """Generate metadata section.
447
+
448
+ Args:
449
+ template: Plugin template configuration.
450
+ entry_point_group: Entry point group name.
383
451
 
452
+ Returns:
453
+ Metadata section content.
454
+ """
455
+ return textwrap.dedent(f"""\
384
456
  ## Plugin Type: {template.plugin_type}
385
457
 
386
458
  This is a **{template.plugin_type}** plugin for Oscura.
@@ -407,8 +479,6 @@ def _write_readme(template: PluginTemplate) -> None:
407
479
  {template.version}
408
480
  """)
409
481
 
410
- (template.output_dir / "README.md").write_text(content)
411
-
412
482
 
413
483
  def _write_pyproject_toml(template: PluginTemplate) -> None:
414
484
  """Write pyproject.toml for plugin packaging.