okstra 0.130.3 → 0.131.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 (367) hide show
  1. package/README.md +0 -1
  2. package/docs/architecture.md +2 -2
  3. package/docs/cli.md +1 -1
  4. package/docs/for-ai/README.md +0 -2
  5. package/docs/for-ai/skills/okstra-run.md +13 -2
  6. package/docs/for-ai/skills/okstra-setup.md +4 -1
  7. package/docs/project-structure-overview.md +5 -4
  8. package/docs/task-process/implementation-planning.md +13 -0
  9. package/docs/task-process/implementation.md +17 -2
  10. package/package.json +1 -1
  11. package/runtime/BUILD.json +2 -2
  12. package/runtime/agents/workers/report-writer-worker.md +7 -0
  13. package/runtime/bin/lib/okstra/usage.sh +4 -4
  14. package/runtime/prompts/lead/okstra-lead-contract.md +2 -2
  15. package/runtime/prompts/lead/plan-body-verification.md +26 -5
  16. package/runtime/prompts/lead/report-writer.md +6 -2
  17. package/runtime/prompts/profiles/_common-contract.md +2 -1
  18. package/runtime/prompts/profiles/_implementation-deliverable.md +6 -1
  19. package/runtime/prompts/profiles/_implementation-executor.md +2 -2
  20. package/runtime/prompts/profiles/_implementation-verifier.md +33 -10
  21. package/runtime/prompts/profiles/final-verification.md +21 -6
  22. package/runtime/prompts/profiles/implementation-planning.md +21 -4
  23. package/runtime/python/okstra_ctl/codex_dispatch.py +31 -0
  24. package/runtime/python/okstra_ctl/conformance.py +49 -11
  25. package/runtime/python/okstra_ctl/report_finalize.py +52 -1
  26. package/runtime/python/okstra_ctl/report_views.py +32 -5
  27. package/runtime/python/okstra_ctl/run.py +15 -3
  28. package/runtime/python/okstra_ctl/worker_prompt_contract.py +14 -6
  29. package/runtime/python/okstra_ctl/worker_prompt_policy.py +19 -1
  30. package/runtime/python/okstra_vendor/__init__.py +0 -44
  31. package/runtime/schemas/final-report-v1.0.schema.json +101 -6
  32. package/runtime/skills/okstra-run/SKILL.md +11 -4
  33. package/runtime/skills/okstra-setup/SKILL.md +3 -2
  34. package/runtime/skills/okstra-setup/references/project-config.md +16 -7
  35. package/runtime/templates/reports/final-report.template.md +18 -12
  36. package/runtime/templates/reports/i18n/en.json +1 -0
  37. package/runtime/templates/reports/i18n/ko.json +1 -0
  38. package/runtime/templates/reports/report.css +18 -0
  39. package/runtime/validators/lib/fixtures.sh +6 -0
  40. package/runtime/validators/validate-run.py +1722 -98
  41. package/src/cli-registry.mjs +0 -7
  42. package/src/commands/lifecycle/doctor.mjs +0 -9
  43. package/src/lib/skill-catalog.mjs +0 -1
  44. package/docs/for-ai/skills/okstra-graphify.md +0 -64
  45. package/runtime/python/okstra_ctl/graphify_cmd.py +0 -225
  46. package/runtime/python/okstra_vendor/graphify/.vendored-version +0 -1
  47. package/runtime/python/okstra_vendor/graphify/__init__.py +0 -28
  48. package/runtime/python/okstra_vendor/graphify/__main__.py +0 -1371
  49. package/runtime/python/okstra_vendor/graphify/analyze.py +0 -540
  50. package/runtime/python/okstra_vendor/graphify/benchmark.py +0 -129
  51. package/runtime/python/okstra_vendor/graphify/build.py +0 -107
  52. package/runtime/python/okstra_vendor/graphify/cache.py +0 -169
  53. package/runtime/python/okstra_vendor/graphify/cluster.py +0 -137
  54. package/runtime/python/okstra_vendor/graphify/detect.py +0 -510
  55. package/runtime/python/okstra_vendor/graphify/export.py +0 -1014
  56. package/runtime/python/okstra_vendor/graphify/extract.py +0 -3277
  57. package/runtime/python/okstra_vendor/graphify/hooks.py +0 -220
  58. package/runtime/python/okstra_vendor/graphify/ingest.py +0 -297
  59. package/runtime/python/okstra_vendor/graphify/manifest.py +0 -4
  60. package/runtime/python/okstra_vendor/graphify/report.py +0 -175
  61. package/runtime/python/okstra_vendor/graphify/security.py +0 -203
  62. package/runtime/python/okstra_vendor/graphify/serve.py +0 -373
  63. package/runtime/python/okstra_vendor/graphify/skill-aider.md +0 -1184
  64. package/runtime/python/okstra_vendor/graphify/skill-claw.md +0 -1184
  65. package/runtime/python/okstra_vendor/graphify/skill-codex.md +0 -1242
  66. package/runtime/python/okstra_vendor/graphify/skill-copilot.md +0 -1268
  67. package/runtime/python/okstra_vendor/graphify/skill-droid.md +0 -1239
  68. package/runtime/python/okstra_vendor/graphify/skill-kiro.md +0 -1183
  69. package/runtime/python/okstra_vendor/graphify/skill-opencode.md +0 -1238
  70. package/runtime/python/okstra_vendor/graphify/skill-trae.md +0 -1208
  71. package/runtime/python/okstra_vendor/graphify/skill-vscode.md +0 -253
  72. package/runtime/python/okstra_vendor/graphify/skill-windows.md +0 -1245
  73. package/runtime/python/okstra_vendor/graphify/skill.md +0 -1319
  74. package/runtime/python/okstra_vendor/graphify/transcribe.py +0 -182
  75. package/runtime/python/okstra_vendor/graphify/validate.py +0 -72
  76. package/runtime/python/okstra_vendor/graphify/watch.py +0 -188
  77. package/runtime/python/okstra_vendor/graphify/wiki.py +0 -214
  78. package/runtime/python/okstra_vendor/networkx/__init__.py +0 -62
  79. package/runtime/python/okstra_vendor/networkx/algorithms/__init__.py +0 -134
  80. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/__init__.py +0 -26
  81. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/clique.py +0 -259
  82. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/clustering_coefficient.py +0 -71
  83. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/connectivity.py +0 -412
  84. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/density.py +0 -396
  85. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/distance_measures.py +0 -150
  86. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/dominating_set.py +0 -149
  87. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/kcomponents.py +0 -369
  88. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/matching.py +0 -44
  89. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/maxcut.py +0 -143
  90. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/ramsey.py +0 -53
  91. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/steinertree.py +0 -265
  92. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/traveling_salesman.py +0 -1508
  93. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/treewidth.py +0 -255
  94. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/vertex_cover.py +0 -83
  95. package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/__init__.py +0 -5
  96. package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/connectivity.py +0 -122
  97. package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/correlation.py +0 -302
  98. package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/mixing.py +0 -255
  99. package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/neighbor_degree.py +0 -160
  100. package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/pairs.py +0 -127
  101. package/runtime/python/okstra_vendor/networkx/algorithms/asteroidal.py +0 -164
  102. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/__init__.py +0 -88
  103. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/basic.py +0 -322
  104. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/centrality.py +0 -290
  105. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/cluster.py +0 -289
  106. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/covering.py +0 -57
  107. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/edgelist.py +0 -360
  108. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/extendability.py +0 -105
  109. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/generators.py +0 -603
  110. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/link_analysis.py +0 -316
  111. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/matching.py +0 -590
  112. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/matrix.py +0 -232
  113. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/projection.py +0 -526
  114. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/redundancy.py +0 -112
  115. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/spectral.py +0 -69
  116. package/runtime/python/okstra_vendor/networkx/algorithms/boundary.py +0 -168
  117. package/runtime/python/okstra_vendor/networkx/algorithms/bridges.py +0 -205
  118. package/runtime/python/okstra_vendor/networkx/algorithms/broadcasting.py +0 -164
  119. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/__init__.py +0 -20
  120. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/betweenness.py +0 -591
  121. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/betweenness_subset.py +0 -236
  122. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/closeness.py +0 -282
  123. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/current_flow_betweenness.py +0 -364
  124. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/current_flow_betweenness_subset.py +0 -227
  125. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/current_flow_closeness.py +0 -96
  126. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/degree_alg.py +0 -150
  127. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/dispersion.py +0 -107
  128. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/eigenvector.py +0 -357
  129. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/flow_matrix.py +0 -130
  130. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/group.py +0 -787
  131. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/harmonic.py +0 -88
  132. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/katz.py +0 -331
  133. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/laplacian.py +0 -150
  134. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/load.py +0 -200
  135. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/percolation.py +0 -128
  136. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/reaching.py +0 -209
  137. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/second_order.py +0 -141
  138. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/subgraph_alg.py +0 -361
  139. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/trophic.py +0 -181
  140. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/voterank_alg.py +0 -95
  141. package/runtime/python/okstra_vendor/networkx/algorithms/chains.py +0 -172
  142. package/runtime/python/okstra_vendor/networkx/algorithms/chordal.py +0 -443
  143. package/runtime/python/okstra_vendor/networkx/algorithms/clique.py +0 -818
  144. package/runtime/python/okstra_vendor/networkx/algorithms/cluster.py +0 -732
  145. package/runtime/python/okstra_vendor/networkx/algorithms/coloring/__init__.py +0 -4
  146. package/runtime/python/okstra_vendor/networkx/algorithms/coloring/equitable_coloring.py +0 -505
  147. package/runtime/python/okstra_vendor/networkx/algorithms/coloring/greedy_coloring.py +0 -565
  148. package/runtime/python/okstra_vendor/networkx/algorithms/communicability_alg.py +0 -163
  149. package/runtime/python/okstra_vendor/networkx/algorithms/community/__init__.py +0 -28
  150. package/runtime/python/okstra_vendor/networkx/algorithms/community/asyn_fluid.py +0 -153
  151. package/runtime/python/okstra_vendor/networkx/algorithms/community/bipartitions.py +0 -354
  152. package/runtime/python/okstra_vendor/networkx/algorithms/community/centrality.py +0 -171
  153. package/runtime/python/okstra_vendor/networkx/algorithms/community/community_utils.py +0 -30
  154. package/runtime/python/okstra_vendor/networkx/algorithms/community/divisive.py +0 -216
  155. package/runtime/python/okstra_vendor/networkx/algorithms/community/kclique.py +0 -79
  156. package/runtime/python/okstra_vendor/networkx/algorithms/community/label_propagation.py +0 -338
  157. package/runtime/python/okstra_vendor/networkx/algorithms/community/leiden.py +0 -162
  158. package/runtime/python/okstra_vendor/networkx/algorithms/community/local.py +0 -220
  159. package/runtime/python/okstra_vendor/networkx/algorithms/community/louvain.py +0 -384
  160. package/runtime/python/okstra_vendor/networkx/algorithms/community/lukes.py +0 -227
  161. package/runtime/python/okstra_vendor/networkx/algorithms/community/modularity_max.py +0 -452
  162. package/runtime/python/okstra_vendor/networkx/algorithms/community/quality.py +0 -347
  163. package/runtime/python/okstra_vendor/networkx/algorithms/components/__init__.py +0 -6
  164. package/runtime/python/okstra_vendor/networkx/algorithms/components/attracting.py +0 -115
  165. package/runtime/python/okstra_vendor/networkx/algorithms/components/biconnected.py +0 -394
  166. package/runtime/python/okstra_vendor/networkx/algorithms/components/connected.py +0 -282
  167. package/runtime/python/okstra_vendor/networkx/algorithms/components/semiconnected.py +0 -71
  168. package/runtime/python/okstra_vendor/networkx/algorithms/components/strongly_connected.py +0 -359
  169. package/runtime/python/okstra_vendor/networkx/algorithms/components/weakly_connected.py +0 -196
  170. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/__init__.py +0 -11
  171. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/connectivity.py +0 -811
  172. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/cuts.py +0 -616
  173. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/disjoint_paths.py +0 -408
  174. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/edge_augmentation.py +0 -1270
  175. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/edge_kcomponents.py +0 -592
  176. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/kcomponents.py +0 -220
  177. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/kcutsets.py +0 -235
  178. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/stoerwagner.py +0 -152
  179. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/utils.py +0 -88
  180. package/runtime/python/okstra_vendor/networkx/algorithms/core.py +0 -588
  181. package/runtime/python/okstra_vendor/networkx/algorithms/covering.py +0 -142
  182. package/runtime/python/okstra_vendor/networkx/algorithms/cuts.py +0 -416
  183. package/runtime/python/okstra_vendor/networkx/algorithms/cycles.py +0 -1234
  184. package/runtime/python/okstra_vendor/networkx/algorithms/d_separation.py +0 -677
  185. package/runtime/python/okstra_vendor/networkx/algorithms/dag.py +0 -1392
  186. package/runtime/python/okstra_vendor/networkx/algorithms/distance_measures.py +0 -1095
  187. package/runtime/python/okstra_vendor/networkx/algorithms/distance_regular.py +0 -272
  188. package/runtime/python/okstra_vendor/networkx/algorithms/dominance.py +0 -142
  189. package/runtime/python/okstra_vendor/networkx/algorithms/dominating.py +0 -268
  190. package/runtime/python/okstra_vendor/networkx/algorithms/efficiency_measures.py +0 -167
  191. package/runtime/python/okstra_vendor/networkx/algorithms/euler.py +0 -470
  192. package/runtime/python/okstra_vendor/networkx/algorithms/flow/__init__.py +0 -11
  193. package/runtime/python/okstra_vendor/networkx/algorithms/flow/boykovkolmogorov.py +0 -370
  194. package/runtime/python/okstra_vendor/networkx/algorithms/flow/capacityscaling.py +0 -407
  195. package/runtime/python/okstra_vendor/networkx/algorithms/flow/dinitz_alg.py +0 -238
  196. package/runtime/python/okstra_vendor/networkx/algorithms/flow/edmondskarp.py +0 -241
  197. package/runtime/python/okstra_vendor/networkx/algorithms/flow/gomory_hu.py +0 -178
  198. package/runtime/python/okstra_vendor/networkx/algorithms/flow/maxflow.py +0 -611
  199. package/runtime/python/okstra_vendor/networkx/algorithms/flow/mincost.py +0 -356
  200. package/runtime/python/okstra_vendor/networkx/algorithms/flow/networksimplex.py +0 -662
  201. package/runtime/python/okstra_vendor/networkx/algorithms/flow/preflowpush.py +0 -425
  202. package/runtime/python/okstra_vendor/networkx/algorithms/flow/shortestaugmentingpath.py +0 -300
  203. package/runtime/python/okstra_vendor/networkx/algorithms/flow/utils.py +0 -194
  204. package/runtime/python/okstra_vendor/networkx/algorithms/graph_hashing.py +0 -435
  205. package/runtime/python/okstra_vendor/networkx/algorithms/graphical.py +0 -483
  206. package/runtime/python/okstra_vendor/networkx/algorithms/hierarchy.py +0 -57
  207. package/runtime/python/okstra_vendor/networkx/algorithms/hybrid.py +0 -196
  208. package/runtime/python/okstra_vendor/networkx/algorithms/isolate.py +0 -107
  209. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/__init__.py +0 -7
  210. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/ismags.py +0 -1306
  211. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/isomorph.py +0 -336
  212. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/isomorphvf2.py +0 -1262
  213. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/matchhelpers.py +0 -352
  214. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/temporalisomorphvf2.py +0 -308
  215. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tree_isomorphism.py +0 -264
  216. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/vf2pp.py +0 -1102
  217. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/vf2userfunc.py +0 -192
  218. package/runtime/python/okstra_vendor/networkx/algorithms/link_analysis/__init__.py +0 -2
  219. package/runtime/python/okstra_vendor/networkx/algorithms/link_analysis/hits_alg.py +0 -337
  220. package/runtime/python/okstra_vendor/networkx/algorithms/link_analysis/pagerank_alg.py +0 -498
  221. package/runtime/python/okstra_vendor/networkx/algorithms/link_prediction.py +0 -687
  222. package/runtime/python/okstra_vendor/networkx/algorithms/lowest_common_ancestors.py +0 -280
  223. package/runtime/python/okstra_vendor/networkx/algorithms/matching.py +0 -1148
  224. package/runtime/python/okstra_vendor/networkx/algorithms/minors/__init__.py +0 -27
  225. package/runtime/python/okstra_vendor/networkx/algorithms/minors/contraction.py +0 -738
  226. package/runtime/python/okstra_vendor/networkx/algorithms/mis.py +0 -78
  227. package/runtime/python/okstra_vendor/networkx/algorithms/moral.py +0 -59
  228. package/runtime/python/okstra_vendor/networkx/algorithms/node_classification.py +0 -219
  229. package/runtime/python/okstra_vendor/networkx/algorithms/non_randomness.py +0 -155
  230. package/runtime/python/okstra_vendor/networkx/algorithms/operators/__init__.py +0 -4
  231. package/runtime/python/okstra_vendor/networkx/algorithms/operators/all.py +0 -324
  232. package/runtime/python/okstra_vendor/networkx/algorithms/operators/binary.py +0 -468
  233. package/runtime/python/okstra_vendor/networkx/algorithms/operators/product.py +0 -633
  234. package/runtime/python/okstra_vendor/networkx/algorithms/operators/unary.py +0 -77
  235. package/runtime/python/okstra_vendor/networkx/algorithms/perfect_graph.py +0 -73
  236. package/runtime/python/okstra_vendor/networkx/algorithms/planar_drawing.py +0 -464
  237. package/runtime/python/okstra_vendor/networkx/algorithms/planarity.py +0 -1463
  238. package/runtime/python/okstra_vendor/networkx/algorithms/polynomials.py +0 -306
  239. package/runtime/python/okstra_vendor/networkx/algorithms/reciprocity.py +0 -98
  240. package/runtime/python/okstra_vendor/networkx/algorithms/regular.py +0 -167
  241. package/runtime/python/okstra_vendor/networkx/algorithms/richclub.py +0 -138
  242. package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/__init__.py +0 -5
  243. package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/astar.py +0 -239
  244. package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/dense.py +0 -264
  245. package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/generic.py +0 -716
  246. package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/unweighted.py +0 -625
  247. package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/weighted.py +0 -2542
  248. package/runtime/python/okstra_vendor/networkx/algorithms/similarity.py +0 -2107
  249. package/runtime/python/okstra_vendor/networkx/algorithms/simple_paths.py +0 -966
  250. package/runtime/python/okstra_vendor/networkx/algorithms/smallworld.py +0 -404
  251. package/runtime/python/okstra_vendor/networkx/algorithms/smetric.py +0 -30
  252. package/runtime/python/okstra_vendor/networkx/algorithms/sparsifiers.py +0 -296
  253. package/runtime/python/okstra_vendor/networkx/algorithms/structuralholes.py +0 -374
  254. package/runtime/python/okstra_vendor/networkx/algorithms/summarization.py +0 -564
  255. package/runtime/python/okstra_vendor/networkx/algorithms/swap.py +0 -406
  256. package/runtime/python/okstra_vendor/networkx/algorithms/threshold.py +0 -981
  257. package/runtime/python/okstra_vendor/networkx/algorithms/time_dependent.py +0 -142
  258. package/runtime/python/okstra_vendor/networkx/algorithms/tournament.py +0 -406
  259. package/runtime/python/okstra_vendor/networkx/algorithms/traversal/__init__.py +0 -5
  260. package/runtime/python/okstra_vendor/networkx/algorithms/traversal/beamsearch.py +0 -90
  261. package/runtime/python/okstra_vendor/networkx/algorithms/traversal/breadth_first_search.py +0 -576
  262. package/runtime/python/okstra_vendor/networkx/algorithms/traversal/depth_first_search.py +0 -529
  263. package/runtime/python/okstra_vendor/networkx/algorithms/traversal/edgebfs.py +0 -185
  264. package/runtime/python/okstra_vendor/networkx/algorithms/traversal/edgedfs.py +0 -182
  265. package/runtime/python/okstra_vendor/networkx/algorithms/tree/__init__.py +0 -7
  266. package/runtime/python/okstra_vendor/networkx/algorithms/tree/branchings.py +0 -1042
  267. package/runtime/python/okstra_vendor/networkx/algorithms/tree/coding.py +0 -413
  268. package/runtime/python/okstra_vendor/networkx/algorithms/tree/decomposition.py +0 -88
  269. package/runtime/python/okstra_vendor/networkx/algorithms/tree/distance_measures.py +0 -219
  270. package/runtime/python/okstra_vendor/networkx/algorithms/tree/mst.py +0 -1281
  271. package/runtime/python/okstra_vendor/networkx/algorithms/tree/operations.py +0 -106
  272. package/runtime/python/okstra_vendor/networkx/algorithms/tree/recognition.py +0 -273
  273. package/runtime/python/okstra_vendor/networkx/algorithms/triads.py +0 -500
  274. package/runtime/python/okstra_vendor/networkx/algorithms/vitality.py +0 -76
  275. package/runtime/python/okstra_vendor/networkx/algorithms/voronoi.py +0 -86
  276. package/runtime/python/okstra_vendor/networkx/algorithms/walks.py +0 -77
  277. package/runtime/python/okstra_vendor/networkx/algorithms/wiener.py +0 -278
  278. package/runtime/python/okstra_vendor/networkx/classes/__init__.py +0 -13
  279. package/runtime/python/okstra_vendor/networkx/classes/coreviews.py +0 -435
  280. package/runtime/python/okstra_vendor/networkx/classes/digraph.py +0 -1363
  281. package/runtime/python/okstra_vendor/networkx/classes/filters.py +0 -95
  282. package/runtime/python/okstra_vendor/networkx/classes/function.py +0 -1549
  283. package/runtime/python/okstra_vendor/networkx/classes/graph.py +0 -2082
  284. package/runtime/python/okstra_vendor/networkx/classes/graphviews.py +0 -269
  285. package/runtime/python/okstra_vendor/networkx/classes/multidigraph.py +0 -977
  286. package/runtime/python/okstra_vendor/networkx/classes/multigraph.py +0 -1294
  287. package/runtime/python/okstra_vendor/networkx/classes/reportviews.py +0 -1447
  288. package/runtime/python/okstra_vendor/networkx/convert.py +0 -502
  289. package/runtime/python/okstra_vendor/networkx/convert_matrix.py +0 -1314
  290. package/runtime/python/okstra_vendor/networkx/drawing/__init__.py +0 -7
  291. package/runtime/python/okstra_vendor/networkx/drawing/layout.py +0 -2036
  292. package/runtime/python/okstra_vendor/networkx/drawing/nx_agraph.py +0 -470
  293. package/runtime/python/okstra_vendor/networkx/drawing/nx_latex.py +0 -570
  294. package/runtime/python/okstra_vendor/networkx/drawing/nx_pydot.py +0 -361
  295. package/runtime/python/okstra_vendor/networkx/drawing/nx_pylab.py +0 -2978
  296. package/runtime/python/okstra_vendor/networkx/exception.py +0 -131
  297. package/runtime/python/okstra_vendor/networkx/generators/__init__.py +0 -34
  298. package/runtime/python/okstra_vendor/networkx/generators/atlas.dat.gz +0 -0
  299. package/runtime/python/okstra_vendor/networkx/generators/atlas.py +0 -227
  300. package/runtime/python/okstra_vendor/networkx/generators/classic.py +0 -1091
  301. package/runtime/python/okstra_vendor/networkx/generators/cographs.py +0 -68
  302. package/runtime/python/okstra_vendor/networkx/generators/community.py +0 -1070
  303. package/runtime/python/okstra_vendor/networkx/generators/degree_seq.py +0 -886
  304. package/runtime/python/okstra_vendor/networkx/generators/directed.py +0 -572
  305. package/runtime/python/okstra_vendor/networkx/generators/duplication.py +0 -174
  306. package/runtime/python/okstra_vendor/networkx/generators/ego.py +0 -66
  307. package/runtime/python/okstra_vendor/networkx/generators/expanders.py +0 -499
  308. package/runtime/python/okstra_vendor/networkx/generators/geometric.py +0 -1037
  309. package/runtime/python/okstra_vendor/networkx/generators/harary_graph.py +0 -163
  310. package/runtime/python/okstra_vendor/networkx/generators/internet_as_graphs.py +0 -443
  311. package/runtime/python/okstra_vendor/networkx/generators/intersection.py +0 -125
  312. package/runtime/python/okstra_vendor/networkx/generators/interval_graph.py +0 -70
  313. package/runtime/python/okstra_vendor/networkx/generators/joint_degree_seq.py +0 -664
  314. package/runtime/python/okstra_vendor/networkx/generators/lattice.py +0 -405
  315. package/runtime/python/okstra_vendor/networkx/generators/line.py +0 -501
  316. package/runtime/python/okstra_vendor/networkx/generators/mycielski.py +0 -110
  317. package/runtime/python/okstra_vendor/networkx/generators/nonisomorphic_trees.py +0 -259
  318. package/runtime/python/okstra_vendor/networkx/generators/random_clustered.py +0 -117
  319. package/runtime/python/okstra_vendor/networkx/generators/random_graphs.py +0 -1416
  320. package/runtime/python/okstra_vendor/networkx/generators/small.py +0 -1070
  321. package/runtime/python/okstra_vendor/networkx/generators/social.py +0 -554
  322. package/runtime/python/okstra_vendor/networkx/generators/spectral_graph_forge.py +0 -120
  323. package/runtime/python/okstra_vendor/networkx/generators/stochastic.py +0 -54
  324. package/runtime/python/okstra_vendor/networkx/generators/sudoku.py +0 -131
  325. package/runtime/python/okstra_vendor/networkx/generators/time_series.py +0 -74
  326. package/runtime/python/okstra_vendor/networkx/generators/trees.py +0 -1070
  327. package/runtime/python/okstra_vendor/networkx/generators/triads.py +0 -94
  328. package/runtime/python/okstra_vendor/networkx/lazy_imports.py +0 -188
  329. package/runtime/python/okstra_vendor/networkx/linalg/__init__.py +0 -13
  330. package/runtime/python/okstra_vendor/networkx/linalg/algebraicconnectivity.py +0 -650
  331. package/runtime/python/okstra_vendor/networkx/linalg/attrmatrix.py +0 -466
  332. package/runtime/python/okstra_vendor/networkx/linalg/bethehessianmatrix.py +0 -77
  333. package/runtime/python/okstra_vendor/networkx/linalg/graphmatrix.py +0 -168
  334. package/runtime/python/okstra_vendor/networkx/linalg/laplacianmatrix.py +0 -512
  335. package/runtime/python/okstra_vendor/networkx/linalg/modularitymatrix.py +0 -166
  336. package/runtime/python/okstra_vendor/networkx/linalg/spectrum.py +0 -186
  337. package/runtime/python/okstra_vendor/networkx/readwrite/__init__.py +0 -17
  338. package/runtime/python/okstra_vendor/networkx/readwrite/adjlist.py +0 -330
  339. package/runtime/python/okstra_vendor/networkx/readwrite/edgelist.py +0 -489
  340. package/runtime/python/okstra_vendor/networkx/readwrite/gexf.py +0 -1084
  341. package/runtime/python/okstra_vendor/networkx/readwrite/gml.py +0 -879
  342. package/runtime/python/okstra_vendor/networkx/readwrite/graph6.py +0 -427
  343. package/runtime/python/okstra_vendor/networkx/readwrite/graphml.py +0 -1053
  344. package/runtime/python/okstra_vendor/networkx/readwrite/json_graph/__init__.py +0 -19
  345. package/runtime/python/okstra_vendor/networkx/readwrite/json_graph/adjacency.py +0 -156
  346. package/runtime/python/okstra_vendor/networkx/readwrite/json_graph/cytoscape.py +0 -190
  347. package/runtime/python/okstra_vendor/networkx/readwrite/json_graph/node_link.py +0 -261
  348. package/runtime/python/okstra_vendor/networkx/readwrite/json_graph/tree.py +0 -137
  349. package/runtime/python/okstra_vendor/networkx/readwrite/leda.py +0 -108
  350. package/runtime/python/okstra_vendor/networkx/readwrite/multiline_adjlist.py +0 -393
  351. package/runtime/python/okstra_vendor/networkx/readwrite/p2g.py +0 -113
  352. package/runtime/python/okstra_vendor/networkx/readwrite/pajek.py +0 -286
  353. package/runtime/python/okstra_vendor/networkx/readwrite/sparse6.py +0 -379
  354. package/runtime/python/okstra_vendor/networkx/readwrite/text.py +0 -851
  355. package/runtime/python/okstra_vendor/networkx/relabel.py +0 -285
  356. package/runtime/python/okstra_vendor/networkx/utils/__init__.py +0 -8
  357. package/runtime/python/okstra_vendor/networkx/utils/backends.py +0 -2171
  358. package/runtime/python/okstra_vendor/networkx/utils/configs.py +0 -396
  359. package/runtime/python/okstra_vendor/networkx/utils/decorators.py +0 -1233
  360. package/runtime/python/okstra_vendor/networkx/utils/heaps.py +0 -338
  361. package/runtime/python/okstra_vendor/networkx/utils/mapped_queue.py +0 -297
  362. package/runtime/python/okstra_vendor/networkx/utils/misc.py +0 -703
  363. package/runtime/python/okstra_vendor/networkx/utils/random_sequence.py +0 -198
  364. package/runtime/python/okstra_vendor/networkx/utils/rcm.py +0 -159
  365. package/runtime/python/okstra_vendor/networkx/utils/union_find.py +0 -106
  366. package/runtime/skills/okstra-graphify/SKILL.md +0 -169
  367. package/src/commands/graphify.mjs +0 -32
@@ -1,851 +0,0 @@
1
- """
2
- Text-based visual representations of graphs
3
- """
4
-
5
- import sys
6
- from collections import defaultdict
7
-
8
- import networkx as nx
9
- from networkx.utils import open_file
10
-
11
- __all__ = ["generate_network_text", "write_network_text"]
12
-
13
-
14
- class BaseGlyphs:
15
- @classmethod
16
- def as_dict(cls):
17
- return {
18
- a: getattr(cls, a)
19
- for a in dir(cls)
20
- if not a.startswith("_") and a != "as_dict"
21
- }
22
-
23
-
24
- class AsciiBaseGlyphs(BaseGlyphs):
25
- empty: str = "+"
26
- newtree_last: str = "+-- "
27
- newtree_mid: str = "+-- "
28
- endof_forest: str = " "
29
- within_forest: str = ": "
30
- within_tree: str = "| "
31
-
32
-
33
- class AsciiDirectedGlyphs(AsciiBaseGlyphs):
34
- last: str = "L-> "
35
- mid: str = "|-> "
36
- backedge: str = "<-"
37
- vertical_edge: str = "!"
38
-
39
-
40
- class AsciiUndirectedGlyphs(AsciiBaseGlyphs):
41
- last: str = "L-- "
42
- mid: str = "|-- "
43
- backedge: str = "-"
44
- vertical_edge: str = "|"
45
-
46
-
47
- class UtfBaseGlyphs(BaseGlyphs):
48
- # Notes on available box and arrow characters
49
- # https://en.wikipedia.org/wiki/Box-drawing_character
50
- # https://stackoverflow.com/questions/2701192/triangle-arrow
51
- empty: str = "╙"
52
- newtree_last: str = "╙── "
53
- newtree_mid: str = "╟── "
54
- endof_forest: str = " "
55
- within_forest: str = "╎ "
56
- within_tree: str = "│ "
57
-
58
-
59
- class UtfDirectedGlyphs(UtfBaseGlyphs):
60
- last: str = "└─╼ "
61
- mid: str = "├─╼ "
62
- backedge: str = "╾"
63
- vertical_edge: str = "╽"
64
-
65
-
66
- class UtfUndirectedGlyphs(UtfBaseGlyphs):
67
- last: str = "└── "
68
- mid: str = "├── "
69
- backedge: str = "─"
70
- vertical_edge: str = "│"
71
-
72
-
73
- def generate_network_text(
74
- graph,
75
- with_labels=True,
76
- sources=None,
77
- max_depth=None,
78
- ascii_only=False,
79
- vertical_chains=False,
80
- ):
81
- """Generate lines in the "network text" format
82
-
83
- This works via a depth-first traversal of the graph and writing a line for
84
- each unique node encountered. Non-tree edges are written to the right of
85
- each node, and connection to a non-tree edge is indicated with an ellipsis.
86
- This representation works best when the input graph is a forest, but any
87
- graph can be represented.
88
-
89
- This notation is original to networkx, although it is simple enough that it
90
- may be known in existing literature. See #5602 for details. The procedure
91
- is summarized as follows:
92
-
93
- 1. Given a set of source nodes (which can be specified, or automatically
94
- discovered via finding the (strongly) connected components and choosing one
95
- node with minimum degree from each), we traverse the graph in depth first
96
- order.
97
-
98
- 2. Each reachable node will be printed exactly once on it's own line.
99
-
100
- 3. Edges are indicated in one of four ways:
101
-
102
- a. a parent "L-style" connection on the upper left. This corresponds to
103
- a traversal in the directed DFS tree.
104
-
105
- b. a backref "<-style" connection shown directly on the right. For
106
- directed graphs, these are drawn for any incoming edges to a node that
107
- is not a parent edge. For undirected graphs, these are drawn for only
108
- the non-parent edges that have already been represented (The edges that
109
- have not been represented will be handled in the recursive case).
110
-
111
- c. a child "L-style" connection on the lower right. Drawing of the
112
- children are handled recursively.
113
-
114
- d. if ``vertical_chains`` is true, and a parent node only has one child
115
- a "vertical-style" edge is drawn between them.
116
-
117
- 4. The children of each node (wrt the directed DFS tree) are drawn
118
- underneath and to the right of it. In the case that a child node has already
119
- been drawn the connection is replaced with an ellipsis ("...") to indicate
120
- that there is one or more connections represented elsewhere.
121
-
122
- 5. If a maximum depth is specified, an edge to nodes past this maximum
123
- depth will be represented by an ellipsis.
124
-
125
- 6. If a node has a truthy "collapse" value, then we do not traverse past
126
- that node.
127
-
128
- Parameters
129
- ----------
130
- graph : nx.DiGraph | nx.Graph
131
- Graph to represent
132
-
133
- with_labels : bool | str
134
- If True will use the "label" attribute of a node to display if it
135
- exists otherwise it will use the node value itself. If given as a
136
- string, then that attribute name will be used instead of "label".
137
- Defaults to True.
138
-
139
- sources : List
140
- Specifies which nodes to start traversal from. Note: nodes that are not
141
- reachable from one of these sources may not be shown. If unspecified,
142
- the minimal set of nodes needed to reach all others will be used.
143
-
144
- max_depth : int | None
145
- The maximum depth to traverse before stopping. Defaults to None.
146
-
147
- ascii_only : Boolean
148
- If True only ASCII characters are used to construct the visualization
149
-
150
- vertical_chains : Boolean
151
- If True, chains of nodes will be drawn vertically when possible.
152
-
153
- Yields
154
- ------
155
- str : a line of generated text
156
-
157
- Examples
158
- --------
159
- >>> graph = nx.path_graph(10)
160
- >>> graph.add_node("A")
161
- >>> graph.add_node("B")
162
- >>> graph.add_node("C")
163
- >>> graph.add_node("D")
164
- >>> graph.add_edge(9, "A")
165
- >>> graph.add_edge(9, "B")
166
- >>> graph.add_edge(9, "C")
167
- >>> graph.add_edge("C", "D")
168
- >>> graph.add_edge("C", "E")
169
- >>> graph.add_edge("C", "F")
170
- >>> nx.write_network_text(graph)
171
- ╙── 0
172
- └── 1
173
- └── 2
174
- └── 3
175
- └── 4
176
- └── 5
177
- └── 6
178
- └── 7
179
- └── 8
180
- └── 9
181
- ├── A
182
- ├── B
183
- └── C
184
- ├── D
185
- ├── E
186
- └── F
187
- >>> nx.write_network_text(graph, vertical_chains=True)
188
- ╙── 0
189
-
190
- 1
191
-
192
- 2
193
-
194
- 3
195
-
196
- 4
197
-
198
- 5
199
-
200
- 6
201
-
202
- 7
203
-
204
- 8
205
-
206
- 9
207
- ├── A
208
- ├── B
209
- └── C
210
- ├── D
211
- ├── E
212
- └── F
213
- """
214
- from typing import Any, NamedTuple
215
-
216
- class StackFrame(NamedTuple):
217
- parent: Any
218
- node: Any
219
- indents: list
220
- this_islast: bool
221
- this_vertical: bool
222
-
223
- collapse_attr = "collapse"
224
-
225
- is_directed = graph.is_directed()
226
-
227
- if is_directed:
228
- glyphs = AsciiDirectedGlyphs if ascii_only else UtfDirectedGlyphs
229
- succ = graph.succ
230
- pred = graph.pred
231
- else:
232
- glyphs = AsciiUndirectedGlyphs if ascii_only else UtfUndirectedGlyphs
233
- succ = graph.adj
234
- pred = graph.adj
235
-
236
- if isinstance(with_labels, str):
237
- label_attr = with_labels
238
- elif with_labels:
239
- label_attr = "label"
240
- else:
241
- label_attr = None
242
-
243
- if max_depth == 0:
244
- yield glyphs.empty + " ..."
245
- elif len(graph.nodes) == 0:
246
- yield glyphs.empty
247
- else:
248
- # If the nodes to traverse are unspecified, find the minimal set of
249
- # nodes that will reach the entire graph
250
- if sources is None:
251
- sources = _find_sources(graph)
252
-
253
- # Populate the stack with each:
254
- # 1. parent node in the DFS tree (or None for root nodes),
255
- # 2. the current node in the DFS tree
256
- # 2. a list of indentations indicating depth
257
- # 3. a flag indicating if the node is the final one to be written.
258
- # Reverse the stack so sources are popped in the correct order.
259
- last_idx = len(sources) - 1
260
- stack = [
261
- StackFrame(None, node, [], (idx == last_idx), False)
262
- for idx, node in enumerate(sources)
263
- ][::-1]
264
-
265
- num_skipped_children = defaultdict(lambda: 0)
266
- seen_nodes = set()
267
- while stack:
268
- parent, node, indents, this_islast, this_vertical = stack.pop()
269
-
270
- if node is not Ellipsis:
271
- skip = node in seen_nodes
272
- if skip:
273
- # Mark that we skipped a parent's child
274
- num_skipped_children[parent] += 1
275
-
276
- if this_islast:
277
- # If we reached the last child of a parent, and we skipped
278
- # any of that parents children, then we should emit an
279
- # ellipsis at the end after this.
280
- if num_skipped_children[parent] and parent is not None:
281
- # Append the ellipsis to be emitted last
282
- next_islast = True
283
- try_frame = StackFrame(
284
- node, Ellipsis, indents, next_islast, False
285
- )
286
- stack.append(try_frame)
287
-
288
- # Redo this frame, but not as a last object
289
- next_islast = False
290
- try_frame = StackFrame(
291
- parent, node, indents, next_islast, this_vertical
292
- )
293
- stack.append(try_frame)
294
- continue
295
-
296
- if skip:
297
- continue
298
- seen_nodes.add(node)
299
-
300
- if not indents:
301
- # Top level items (i.e. trees in the forest) get different
302
- # glyphs to indicate they are not actually connected
303
- if this_islast:
304
- this_vertical = False
305
- this_prefix = indents + [glyphs.newtree_last]
306
- next_prefix = indents + [glyphs.endof_forest]
307
- else:
308
- this_prefix = indents + [glyphs.newtree_mid]
309
- next_prefix = indents + [glyphs.within_forest]
310
-
311
- else:
312
- # Non-top-level items
313
- if this_vertical:
314
- this_prefix = indents
315
- next_prefix = indents
316
- else:
317
- if this_islast:
318
- this_prefix = indents + [glyphs.last]
319
- next_prefix = indents + [glyphs.endof_forest]
320
- else:
321
- this_prefix = indents + [glyphs.mid]
322
- next_prefix = indents + [glyphs.within_tree]
323
-
324
- if node is Ellipsis:
325
- label = " ..."
326
- suffix = ""
327
- children = []
328
- else:
329
- if label_attr is not None:
330
- label = str(graph.nodes[node].get(label_attr, node))
331
- else:
332
- label = str(node)
333
-
334
- # Determine if we want to show the children of this node.
335
- if collapse_attr is not None:
336
- collapse = graph.nodes[node].get(collapse_attr, False)
337
- else:
338
- collapse = False
339
-
340
- # Determine:
341
- # (1) children to traverse into after showing this node.
342
- # (2) parents to immediately show to the right of this node.
343
- if is_directed:
344
- # In the directed case we must show every successor node
345
- # note: it may be skipped later, but we don't have that
346
- # information here.
347
- children = list(succ[node])
348
- # In the directed case we must show every predecessor
349
- # except for parent we directly traversed from.
350
- handled_parents = {parent}
351
- else:
352
- # Showing only the unseen children results in a more
353
- # concise representation for the undirected case.
354
- children = [
355
- child for child in succ[node] if child not in seen_nodes
356
- ]
357
-
358
- # In the undirected case, parents are also children, so we
359
- # only need to immediately show the ones we can no longer
360
- # traverse
361
- handled_parents = {*children, parent}
362
-
363
- if max_depth is not None and len(indents) == max_depth - 1:
364
- # Use ellipsis to indicate we have reached maximum depth
365
- if children:
366
- children = [Ellipsis]
367
- handled_parents = {parent}
368
-
369
- if collapse:
370
- # Collapsing a node is the same as reaching maximum depth
371
- if children:
372
- children = [Ellipsis]
373
- handled_parents = {parent}
374
-
375
- # The other parents are other predecessors of this node that
376
- # are not handled elsewhere.
377
- other_parents = [p for p in pred[node] if p not in handled_parents]
378
- if other_parents:
379
- if label_attr is not None:
380
- other_parents_labels = ", ".join(
381
- [
382
- str(graph.nodes[p].get(label_attr, p))
383
- for p in other_parents
384
- ]
385
- )
386
- else:
387
- other_parents_labels = ", ".join(
388
- [str(p) for p in other_parents]
389
- )
390
- suffix = " ".join(["", glyphs.backedge, other_parents_labels])
391
- else:
392
- suffix = ""
393
-
394
- # Emit the line for this node, this will be called for each node
395
- # exactly once.
396
- if this_vertical:
397
- yield "".join(this_prefix + [glyphs.vertical_edge])
398
-
399
- yield "".join(this_prefix + [label, suffix])
400
-
401
- if vertical_chains:
402
- if is_directed:
403
- num_children = len(set(children))
404
- else:
405
- num_children = len(set(children) - {parent})
406
- # The next node can be drawn vertically if it is the only
407
- # remaining child of this node.
408
- next_is_vertical = num_children == 1
409
- else:
410
- next_is_vertical = False
411
-
412
- # Push children on the stack in reverse order so they are popped in
413
- # the original order.
414
- for idx, child in enumerate(children[::-1]):
415
- next_islast = idx == 0
416
- try_frame = StackFrame(
417
- node, child, next_prefix, next_islast, next_is_vertical
418
- )
419
- stack.append(try_frame)
420
-
421
-
422
- @open_file(1, "w")
423
- def write_network_text(
424
- graph,
425
- path=None,
426
- with_labels=True,
427
- sources=None,
428
- max_depth=None,
429
- ascii_only=False,
430
- end="\n",
431
- vertical_chains=False,
432
- ):
433
- """Creates a nice text representation of a graph
434
-
435
- This works via a depth-first traversal of the graph and writing a line for
436
- each unique node encountered. Non-tree edges are written to the right of
437
- each node, and connection to a non-tree edge is indicated with an ellipsis.
438
- This representation works best when the input graph is a forest, but any
439
- graph can be represented.
440
-
441
- Parameters
442
- ----------
443
- graph : nx.DiGraph | nx.Graph
444
- Graph to represent
445
-
446
- path : string or file or callable or None
447
- Filename or file handle for data output.
448
- if a function, then it will be called for each generated line.
449
- if None, this will default to "sys.stdout.write"
450
-
451
- with_labels : bool | str
452
- If True will use the "label" attribute of a node to display if it
453
- exists otherwise it will use the node value itself. If given as a
454
- string, then that attribute name will be used instead of "label".
455
- Defaults to True.
456
-
457
- sources : List
458
- Specifies which nodes to start traversal from. Note: nodes that are not
459
- reachable from one of these sources may not be shown. If unspecified,
460
- the minimal set of nodes needed to reach all others will be used.
461
-
462
- max_depth : int | None
463
- The maximum depth to traverse before stopping. Defaults to None.
464
-
465
- ascii_only : Boolean
466
- If True only ASCII characters are used to construct the visualization
467
-
468
- end : string
469
- The line ending character
470
-
471
- vertical_chains : Boolean
472
- If True, chains of nodes will be drawn vertically when possible.
473
-
474
- Examples
475
- --------
476
- >>> graph = nx.balanced_tree(r=2, h=2, create_using=nx.DiGraph)
477
- >>> nx.write_network_text(graph)
478
- ╙── 0
479
- ├─╼ 1
480
- │ ├─╼ 3
481
- │ └─╼ 4
482
- └─╼ 2
483
- ├─╼ 5
484
- └─╼ 6
485
-
486
- >>> # A near tree with one non-tree edge
487
- >>> graph.add_edge(5, 1)
488
- >>> nx.write_network_text(graph)
489
- ╙── 0
490
- ├─╼ 1 ╾ 5
491
- │ ├─╼ 3
492
- │ └─╼ 4
493
- └─╼ 2
494
- ├─╼ 5
495
- │ └─╼ ...
496
- └─╼ 6
497
-
498
- >>> graph = nx.cycle_graph(5)
499
- >>> nx.write_network_text(graph)
500
- ╙── 0
501
- ├── 1
502
- │ └── 2
503
- │ └── 3
504
- │ └── 4 ─ 0
505
- └── ...
506
-
507
- >>> graph = nx.cycle_graph(5, nx.DiGraph)
508
- >>> nx.write_network_text(graph, vertical_chains=True)
509
- ╙── 0 ╾ 4
510
-
511
- 1
512
-
513
- 2
514
-
515
- 3
516
-
517
- 4
518
- └─╼ ...
519
-
520
- >>> nx.write_network_text(graph, vertical_chains=True, ascii_only=True)
521
- +-- 0 <- 4
522
- !
523
- 1
524
- !
525
- 2
526
- !
527
- 3
528
- !
529
- 4
530
- L-> ...
531
-
532
- >>> graph = nx.generators.barbell_graph(4, 2)
533
- >>> nx.write_network_text(graph, vertical_chains=False)
534
- ╙── 4
535
- ├── 5
536
- │ └── 6
537
- │ ├── 7
538
- │ │ ├── 8 ─ 6
539
- │ │ │ └── 9 ─ 6, 7
540
- │ │ └── ...
541
- │ └── ...
542
- └── 3
543
- ├── 0
544
- │ ├── 1 ─ 3
545
- │ │ └── 2 ─ 0, 3
546
- │ └── ...
547
- └── ...
548
- >>> nx.write_network_text(graph, vertical_chains=True)
549
- ╙── 4
550
- ├── 5
551
- │ │
552
- │ 6
553
- │ ├── 7
554
- │ │ ├── 8 ─ 6
555
- │ │ │ │
556
- │ │ │ 9 ─ 6, 7
557
- │ │ └── ...
558
- │ └── ...
559
- └── 3
560
- ├── 0
561
- │ ├── 1 ─ 3
562
- │ │ │
563
- │ │ 2 ─ 0, 3
564
- │ └── ...
565
- └── ...
566
-
567
- >>> graph = nx.complete_graph(5, create_using=nx.Graph)
568
- >>> nx.write_network_text(graph)
569
- ╙── 0
570
- ├── 1
571
- │ ├── 2 ─ 0
572
- │ │ ├── 3 ─ 0, 1
573
- │ │ │ └── 4 ─ 0, 1, 2
574
- │ │ └── ...
575
- │ └── ...
576
- └── ...
577
-
578
- >>> graph = nx.complete_graph(3, create_using=nx.DiGraph)
579
- >>> nx.write_network_text(graph)
580
- ╙── 0 ╾ 1, 2
581
- ├─╼ 1 ╾ 2
582
- │ ├─╼ 2 ╾ 0
583
- │ │ └─╼ ...
584
- │ └─╼ ...
585
- └─╼ ...
586
- """
587
- if path is None:
588
- # The path is unspecified, write to stdout
589
- _write = sys.stdout.write
590
- elif hasattr(path, "write"):
591
- # The path is already an open file
592
- _write = path.write
593
- elif callable(path):
594
- # The path is a custom callable
595
- _write = path
596
- else:
597
- raise TypeError(type(path))
598
-
599
- for line in generate_network_text(
600
- graph,
601
- with_labels=with_labels,
602
- sources=sources,
603
- max_depth=max_depth,
604
- ascii_only=ascii_only,
605
- vertical_chains=vertical_chains,
606
- ):
607
- _write(line + end)
608
-
609
-
610
- def _find_sources(graph):
611
- """
612
- Determine a minimal set of nodes such that the entire graph is reachable
613
- """
614
- # For each connected part of the graph, choose at least
615
- # one node as a starting point, preferably without a parent
616
- if graph.is_directed():
617
- # Choose one node from each SCC with minimum in_degree
618
- sccs = list(nx.strongly_connected_components(graph))
619
- # condensing the SCCs forms a dag, the nodes in this graph with
620
- # 0 in-degree correspond to the SCCs from which the minimum set
621
- # of nodes from which all other nodes can be reached.
622
- scc_graph = nx.condensation(graph, sccs)
623
- supernode_to_nodes = {sn: [] for sn in scc_graph.nodes()}
624
- # Note: the order of mapping differs between pypy and cpython
625
- # so we have to loop over graph nodes for consistency
626
- mapping = scc_graph.graph["mapping"]
627
- for n in graph.nodes:
628
- sn = mapping[n]
629
- supernode_to_nodes[sn].append(n)
630
- sources = []
631
- for sn in scc_graph.nodes():
632
- if scc_graph.in_degree[sn] == 0:
633
- scc = supernode_to_nodes[sn]
634
- node = min(scc, key=lambda n: graph.in_degree[n])
635
- sources.append(node)
636
- else:
637
- # For undirected graph, the entire graph will be reachable as
638
- # long as we consider one node from every connected component
639
- sources = [
640
- min(cc, key=lambda n: graph.degree[n])
641
- for cc in nx.connected_components(graph)
642
- ]
643
- sources = sorted(sources, key=lambda n: graph.degree[n])
644
- return sources
645
-
646
-
647
- def _parse_network_text(lines):
648
- """Reconstructs a graph from a network text representation.
649
-
650
- This is mainly used for testing. Network text is for display, not
651
- serialization, as such this cannot parse all network text representations
652
- because node labels can be ambiguous with the glyphs and indentation used
653
- to represent edge structure. Additionally, there is no way to determine if
654
- disconnected graphs were originally directed or undirected.
655
-
656
- Parameters
657
- ----------
658
- lines : list or iterator of strings
659
- Input data in network text format
660
-
661
- Returns
662
- -------
663
- G: NetworkX graph
664
- The graph corresponding to the lines in network text format.
665
- """
666
- from itertools import chain
667
- from typing import Any, NamedTuple
668
-
669
- class ParseStackFrame(NamedTuple):
670
- node: Any
671
- indent: int
672
- has_vertical_child: int | None
673
-
674
- initial_line_iter = iter(lines)
675
-
676
- is_ascii = None
677
- is_directed = None
678
-
679
- ##############
680
- # Initial Pass
681
- ##############
682
-
683
- # Do an initial pass over the lines to determine what type of graph it is.
684
- # Remember what these lines were, so we can reiterate over them in the
685
- # parsing pass.
686
- initial_lines = []
687
- try:
688
- first_line = next(initial_line_iter)
689
- except StopIteration:
690
- ...
691
- else:
692
- initial_lines.append(first_line)
693
- # The first character indicates if it is an ASCII or UTF graph
694
- first_char = first_line[0]
695
- if first_char in {
696
- UtfBaseGlyphs.empty,
697
- UtfBaseGlyphs.newtree_mid[0],
698
- UtfBaseGlyphs.newtree_last[0],
699
- }:
700
- is_ascii = False
701
- elif first_char in {
702
- AsciiBaseGlyphs.empty,
703
- AsciiBaseGlyphs.newtree_mid[0],
704
- AsciiBaseGlyphs.newtree_last[0],
705
- }:
706
- is_ascii = True
707
- else:
708
- raise AssertionError(f"Unexpected first character: {first_char}")
709
-
710
- if is_ascii:
711
- directed_glyphs = AsciiDirectedGlyphs.as_dict()
712
- undirected_glyphs = AsciiUndirectedGlyphs.as_dict()
713
- else:
714
- directed_glyphs = UtfDirectedGlyphs.as_dict()
715
- undirected_glyphs = UtfUndirectedGlyphs.as_dict()
716
-
717
- # For both directed / undirected glyphs, determine which glyphs never
718
- # appear as substrings in the other undirected / directed glyphs. Glyphs
719
- # with this property unambiguously indicates if a graph is directed /
720
- # undirected.
721
- directed_items = set(directed_glyphs.values())
722
- undirected_items = set(undirected_glyphs.values())
723
- unambiguous_directed_items = []
724
- for item in directed_items:
725
- other_items = undirected_items
726
- other_supersets = [other for other in other_items if item in other]
727
- if not other_supersets:
728
- unambiguous_directed_items.append(item)
729
- unambiguous_undirected_items = []
730
- for item in undirected_items:
731
- other_items = directed_items
732
- other_supersets = [other for other in other_items if item in other]
733
- if not other_supersets:
734
- unambiguous_undirected_items.append(item)
735
-
736
- for line in initial_line_iter:
737
- initial_lines.append(line)
738
- if any(item in line for item in unambiguous_undirected_items):
739
- is_directed = False
740
- break
741
- elif any(item in line for item in unambiguous_directed_items):
742
- is_directed = True
743
- break
744
-
745
- if is_directed is None:
746
- # Not enough information to determine, choose undirected by default
747
- is_directed = False
748
-
749
- glyphs = directed_glyphs if is_directed else undirected_glyphs
750
-
751
- # the backedge symbol by itself can be ambiguous, but with spaces around it
752
- # becomes unambiguous.
753
- backedge_symbol = " " + glyphs["backedge"] + " "
754
-
755
- # Reconstruct an iterator over all of the lines.
756
- parsing_line_iter = chain(initial_lines, initial_line_iter)
757
-
758
- ##############
759
- # Parsing Pass
760
- ##############
761
-
762
- edges = []
763
- nodes = []
764
- is_empty = None
765
-
766
- noparent = object() # sentinel value
767
-
768
- # keep a stack of previous nodes that could be parents of subsequent nodes
769
- stack = [ParseStackFrame(noparent, -1, None)]
770
-
771
- for line in parsing_line_iter:
772
- if line == glyphs["empty"]:
773
- # If the line is the empty glyph, we are done.
774
- # There shouldn't be anything else after this.
775
- is_empty = True
776
- continue
777
-
778
- if backedge_symbol in line:
779
- # This line has one or more backedges, separate those out
780
- node_part, backedge_part = line.split(backedge_symbol)
781
- backedge_nodes = [u.strip() for u in backedge_part.split(", ")]
782
- # Now the node can be parsed
783
- node_part = node_part.rstrip()
784
- prefix, node = node_part.rsplit(" ", 1)
785
- node = node.strip()
786
- # Add the backedges to the edge list
787
- edges.extend([(u, node) for u in backedge_nodes])
788
- else:
789
- # No backedge, the tail of this line is the node
790
- prefix, node = line.rsplit(" ", 1)
791
- node = node.strip()
792
-
793
- prev = stack.pop()
794
-
795
- if node in glyphs["vertical_edge"]:
796
- # Previous node is still the previous node, but we know it will
797
- # have exactly one child, which will need to have its nesting level
798
- # adjusted.
799
- modified_prev = ParseStackFrame(
800
- prev.node,
801
- prev.indent,
802
- True,
803
- )
804
- stack.append(modified_prev)
805
- continue
806
-
807
- # The length of the string before the node characters give us a hint
808
- # about our nesting level. The only case where this doesn't work is
809
- # when there are vertical chains, which is handled explicitly.
810
- indent = len(prefix)
811
- curr = ParseStackFrame(node, indent, None)
812
-
813
- if prev.has_vertical_child:
814
- # In this case we know prev must be the parent of our current line,
815
- # so we don't have to search the stack. (which is good because the
816
- # indentation check wouldn't work in this case).
817
- ...
818
- else:
819
- # If the previous node nesting-level is greater than the current
820
- # nodes nesting-level than the previous node was the end of a path,
821
- # and is not our parent. We can safely pop nodes off the stack
822
- # until we find one with a comparable nesting-level, which is our
823
- # parent.
824
- while curr.indent <= prev.indent:
825
- prev = stack.pop()
826
-
827
- if node == "...":
828
- # The current previous node is no longer a valid parent,
829
- # keep it popped from the stack.
830
- stack.append(prev)
831
- else:
832
- # The previous and current nodes may still be parents, so add them
833
- # back onto the stack.
834
- stack.append(prev)
835
- stack.append(curr)
836
-
837
- # Add the node and the edge to its parent to the node / edge lists.
838
- nodes.append(curr.node)
839
- if prev.node is not noparent:
840
- edges.append((prev.node, curr.node))
841
-
842
- if is_empty:
843
- # Sanity check
844
- assert len(nodes) == 0
845
-
846
- # Reconstruct the graph
847
- cls = nx.DiGraph if is_directed else nx.Graph
848
- new = cls()
849
- new.add_nodes_from(nodes)
850
- new.add_edges_from(edges)
851
- return new