okstra 0.130.4 → 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 (365) 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 +16 -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/run.py +15 -3
  27. package/runtime/python/okstra_ctl/worker_prompt_contract.py +14 -6
  28. package/runtime/python/okstra_ctl/worker_prompt_policy.py +19 -1
  29. package/runtime/python/okstra_vendor/__init__.py +0 -44
  30. package/runtime/schemas/final-report-v1.0.schema.json +99 -5
  31. package/runtime/skills/okstra-run/SKILL.md +11 -4
  32. package/runtime/skills/okstra-setup/SKILL.md +3 -2
  33. package/runtime/skills/okstra-setup/references/project-config.md +16 -7
  34. package/runtime/templates/reports/final-report.template.md +15 -9
  35. package/runtime/templates/reports/i18n/en.json +1 -0
  36. package/runtime/templates/reports/i18n/ko.json +1 -0
  37. package/runtime/validators/lib/fixtures.sh +6 -0
  38. package/runtime/validators/validate-run.py +1722 -98
  39. package/src/cli-registry.mjs +0 -7
  40. package/src/commands/lifecycle/doctor.mjs +0 -9
  41. package/src/lib/skill-catalog.mjs +0 -1
  42. package/docs/for-ai/skills/okstra-graphify.md +0 -64
  43. package/runtime/python/okstra_ctl/graphify_cmd.py +0 -225
  44. package/runtime/python/okstra_vendor/graphify/.vendored-version +0 -1
  45. package/runtime/python/okstra_vendor/graphify/__init__.py +0 -28
  46. package/runtime/python/okstra_vendor/graphify/__main__.py +0 -1371
  47. package/runtime/python/okstra_vendor/graphify/analyze.py +0 -540
  48. package/runtime/python/okstra_vendor/graphify/benchmark.py +0 -129
  49. package/runtime/python/okstra_vendor/graphify/build.py +0 -107
  50. package/runtime/python/okstra_vendor/graphify/cache.py +0 -169
  51. package/runtime/python/okstra_vendor/graphify/cluster.py +0 -137
  52. package/runtime/python/okstra_vendor/graphify/detect.py +0 -510
  53. package/runtime/python/okstra_vendor/graphify/export.py +0 -1014
  54. package/runtime/python/okstra_vendor/graphify/extract.py +0 -3277
  55. package/runtime/python/okstra_vendor/graphify/hooks.py +0 -220
  56. package/runtime/python/okstra_vendor/graphify/ingest.py +0 -297
  57. package/runtime/python/okstra_vendor/graphify/manifest.py +0 -4
  58. package/runtime/python/okstra_vendor/graphify/report.py +0 -175
  59. package/runtime/python/okstra_vendor/graphify/security.py +0 -203
  60. package/runtime/python/okstra_vendor/graphify/serve.py +0 -373
  61. package/runtime/python/okstra_vendor/graphify/skill-aider.md +0 -1184
  62. package/runtime/python/okstra_vendor/graphify/skill-claw.md +0 -1184
  63. package/runtime/python/okstra_vendor/graphify/skill-codex.md +0 -1242
  64. package/runtime/python/okstra_vendor/graphify/skill-copilot.md +0 -1268
  65. package/runtime/python/okstra_vendor/graphify/skill-droid.md +0 -1239
  66. package/runtime/python/okstra_vendor/graphify/skill-kiro.md +0 -1183
  67. package/runtime/python/okstra_vendor/graphify/skill-opencode.md +0 -1238
  68. package/runtime/python/okstra_vendor/graphify/skill-trae.md +0 -1208
  69. package/runtime/python/okstra_vendor/graphify/skill-vscode.md +0 -253
  70. package/runtime/python/okstra_vendor/graphify/skill-windows.md +0 -1245
  71. package/runtime/python/okstra_vendor/graphify/skill.md +0 -1319
  72. package/runtime/python/okstra_vendor/graphify/transcribe.py +0 -182
  73. package/runtime/python/okstra_vendor/graphify/validate.py +0 -72
  74. package/runtime/python/okstra_vendor/graphify/watch.py +0 -188
  75. package/runtime/python/okstra_vendor/graphify/wiki.py +0 -214
  76. package/runtime/python/okstra_vendor/networkx/__init__.py +0 -62
  77. package/runtime/python/okstra_vendor/networkx/algorithms/__init__.py +0 -134
  78. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/__init__.py +0 -26
  79. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/clique.py +0 -259
  80. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/clustering_coefficient.py +0 -71
  81. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/connectivity.py +0 -412
  82. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/density.py +0 -396
  83. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/distance_measures.py +0 -150
  84. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/dominating_set.py +0 -149
  85. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/kcomponents.py +0 -369
  86. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/matching.py +0 -44
  87. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/maxcut.py +0 -143
  88. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/ramsey.py +0 -53
  89. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/steinertree.py +0 -265
  90. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/traveling_salesman.py +0 -1508
  91. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/treewidth.py +0 -255
  92. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/vertex_cover.py +0 -83
  93. package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/__init__.py +0 -5
  94. package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/connectivity.py +0 -122
  95. package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/correlation.py +0 -302
  96. package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/mixing.py +0 -255
  97. package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/neighbor_degree.py +0 -160
  98. package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/pairs.py +0 -127
  99. package/runtime/python/okstra_vendor/networkx/algorithms/asteroidal.py +0 -164
  100. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/__init__.py +0 -88
  101. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/basic.py +0 -322
  102. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/centrality.py +0 -290
  103. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/cluster.py +0 -289
  104. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/covering.py +0 -57
  105. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/edgelist.py +0 -360
  106. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/extendability.py +0 -105
  107. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/generators.py +0 -603
  108. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/link_analysis.py +0 -316
  109. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/matching.py +0 -590
  110. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/matrix.py +0 -232
  111. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/projection.py +0 -526
  112. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/redundancy.py +0 -112
  113. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/spectral.py +0 -69
  114. package/runtime/python/okstra_vendor/networkx/algorithms/boundary.py +0 -168
  115. package/runtime/python/okstra_vendor/networkx/algorithms/bridges.py +0 -205
  116. package/runtime/python/okstra_vendor/networkx/algorithms/broadcasting.py +0 -164
  117. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/__init__.py +0 -20
  118. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/betweenness.py +0 -591
  119. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/betweenness_subset.py +0 -236
  120. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/closeness.py +0 -282
  121. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/current_flow_betweenness.py +0 -364
  122. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/current_flow_betweenness_subset.py +0 -227
  123. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/current_flow_closeness.py +0 -96
  124. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/degree_alg.py +0 -150
  125. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/dispersion.py +0 -107
  126. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/eigenvector.py +0 -357
  127. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/flow_matrix.py +0 -130
  128. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/group.py +0 -787
  129. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/harmonic.py +0 -88
  130. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/katz.py +0 -331
  131. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/laplacian.py +0 -150
  132. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/load.py +0 -200
  133. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/percolation.py +0 -128
  134. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/reaching.py +0 -209
  135. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/second_order.py +0 -141
  136. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/subgraph_alg.py +0 -361
  137. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/trophic.py +0 -181
  138. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/voterank_alg.py +0 -95
  139. package/runtime/python/okstra_vendor/networkx/algorithms/chains.py +0 -172
  140. package/runtime/python/okstra_vendor/networkx/algorithms/chordal.py +0 -443
  141. package/runtime/python/okstra_vendor/networkx/algorithms/clique.py +0 -818
  142. package/runtime/python/okstra_vendor/networkx/algorithms/cluster.py +0 -732
  143. package/runtime/python/okstra_vendor/networkx/algorithms/coloring/__init__.py +0 -4
  144. package/runtime/python/okstra_vendor/networkx/algorithms/coloring/equitable_coloring.py +0 -505
  145. package/runtime/python/okstra_vendor/networkx/algorithms/coloring/greedy_coloring.py +0 -565
  146. package/runtime/python/okstra_vendor/networkx/algorithms/communicability_alg.py +0 -163
  147. package/runtime/python/okstra_vendor/networkx/algorithms/community/__init__.py +0 -28
  148. package/runtime/python/okstra_vendor/networkx/algorithms/community/asyn_fluid.py +0 -153
  149. package/runtime/python/okstra_vendor/networkx/algorithms/community/bipartitions.py +0 -354
  150. package/runtime/python/okstra_vendor/networkx/algorithms/community/centrality.py +0 -171
  151. package/runtime/python/okstra_vendor/networkx/algorithms/community/community_utils.py +0 -30
  152. package/runtime/python/okstra_vendor/networkx/algorithms/community/divisive.py +0 -216
  153. package/runtime/python/okstra_vendor/networkx/algorithms/community/kclique.py +0 -79
  154. package/runtime/python/okstra_vendor/networkx/algorithms/community/label_propagation.py +0 -338
  155. package/runtime/python/okstra_vendor/networkx/algorithms/community/leiden.py +0 -162
  156. package/runtime/python/okstra_vendor/networkx/algorithms/community/local.py +0 -220
  157. package/runtime/python/okstra_vendor/networkx/algorithms/community/louvain.py +0 -384
  158. package/runtime/python/okstra_vendor/networkx/algorithms/community/lukes.py +0 -227
  159. package/runtime/python/okstra_vendor/networkx/algorithms/community/modularity_max.py +0 -452
  160. package/runtime/python/okstra_vendor/networkx/algorithms/community/quality.py +0 -347
  161. package/runtime/python/okstra_vendor/networkx/algorithms/components/__init__.py +0 -6
  162. package/runtime/python/okstra_vendor/networkx/algorithms/components/attracting.py +0 -115
  163. package/runtime/python/okstra_vendor/networkx/algorithms/components/biconnected.py +0 -394
  164. package/runtime/python/okstra_vendor/networkx/algorithms/components/connected.py +0 -282
  165. package/runtime/python/okstra_vendor/networkx/algorithms/components/semiconnected.py +0 -71
  166. package/runtime/python/okstra_vendor/networkx/algorithms/components/strongly_connected.py +0 -359
  167. package/runtime/python/okstra_vendor/networkx/algorithms/components/weakly_connected.py +0 -196
  168. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/__init__.py +0 -11
  169. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/connectivity.py +0 -811
  170. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/cuts.py +0 -616
  171. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/disjoint_paths.py +0 -408
  172. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/edge_augmentation.py +0 -1270
  173. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/edge_kcomponents.py +0 -592
  174. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/kcomponents.py +0 -220
  175. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/kcutsets.py +0 -235
  176. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/stoerwagner.py +0 -152
  177. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/utils.py +0 -88
  178. package/runtime/python/okstra_vendor/networkx/algorithms/core.py +0 -588
  179. package/runtime/python/okstra_vendor/networkx/algorithms/covering.py +0 -142
  180. package/runtime/python/okstra_vendor/networkx/algorithms/cuts.py +0 -416
  181. package/runtime/python/okstra_vendor/networkx/algorithms/cycles.py +0 -1234
  182. package/runtime/python/okstra_vendor/networkx/algorithms/d_separation.py +0 -677
  183. package/runtime/python/okstra_vendor/networkx/algorithms/dag.py +0 -1392
  184. package/runtime/python/okstra_vendor/networkx/algorithms/distance_measures.py +0 -1095
  185. package/runtime/python/okstra_vendor/networkx/algorithms/distance_regular.py +0 -272
  186. package/runtime/python/okstra_vendor/networkx/algorithms/dominance.py +0 -142
  187. package/runtime/python/okstra_vendor/networkx/algorithms/dominating.py +0 -268
  188. package/runtime/python/okstra_vendor/networkx/algorithms/efficiency_measures.py +0 -167
  189. package/runtime/python/okstra_vendor/networkx/algorithms/euler.py +0 -470
  190. package/runtime/python/okstra_vendor/networkx/algorithms/flow/__init__.py +0 -11
  191. package/runtime/python/okstra_vendor/networkx/algorithms/flow/boykovkolmogorov.py +0 -370
  192. package/runtime/python/okstra_vendor/networkx/algorithms/flow/capacityscaling.py +0 -407
  193. package/runtime/python/okstra_vendor/networkx/algorithms/flow/dinitz_alg.py +0 -238
  194. package/runtime/python/okstra_vendor/networkx/algorithms/flow/edmondskarp.py +0 -241
  195. package/runtime/python/okstra_vendor/networkx/algorithms/flow/gomory_hu.py +0 -178
  196. package/runtime/python/okstra_vendor/networkx/algorithms/flow/maxflow.py +0 -611
  197. package/runtime/python/okstra_vendor/networkx/algorithms/flow/mincost.py +0 -356
  198. package/runtime/python/okstra_vendor/networkx/algorithms/flow/networksimplex.py +0 -662
  199. package/runtime/python/okstra_vendor/networkx/algorithms/flow/preflowpush.py +0 -425
  200. package/runtime/python/okstra_vendor/networkx/algorithms/flow/shortestaugmentingpath.py +0 -300
  201. package/runtime/python/okstra_vendor/networkx/algorithms/flow/utils.py +0 -194
  202. package/runtime/python/okstra_vendor/networkx/algorithms/graph_hashing.py +0 -435
  203. package/runtime/python/okstra_vendor/networkx/algorithms/graphical.py +0 -483
  204. package/runtime/python/okstra_vendor/networkx/algorithms/hierarchy.py +0 -57
  205. package/runtime/python/okstra_vendor/networkx/algorithms/hybrid.py +0 -196
  206. package/runtime/python/okstra_vendor/networkx/algorithms/isolate.py +0 -107
  207. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/__init__.py +0 -7
  208. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/ismags.py +0 -1306
  209. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/isomorph.py +0 -336
  210. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/isomorphvf2.py +0 -1262
  211. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/matchhelpers.py +0 -352
  212. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/temporalisomorphvf2.py +0 -308
  213. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tree_isomorphism.py +0 -264
  214. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/vf2pp.py +0 -1102
  215. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/vf2userfunc.py +0 -192
  216. package/runtime/python/okstra_vendor/networkx/algorithms/link_analysis/__init__.py +0 -2
  217. package/runtime/python/okstra_vendor/networkx/algorithms/link_analysis/hits_alg.py +0 -337
  218. package/runtime/python/okstra_vendor/networkx/algorithms/link_analysis/pagerank_alg.py +0 -498
  219. package/runtime/python/okstra_vendor/networkx/algorithms/link_prediction.py +0 -687
  220. package/runtime/python/okstra_vendor/networkx/algorithms/lowest_common_ancestors.py +0 -280
  221. package/runtime/python/okstra_vendor/networkx/algorithms/matching.py +0 -1148
  222. package/runtime/python/okstra_vendor/networkx/algorithms/minors/__init__.py +0 -27
  223. package/runtime/python/okstra_vendor/networkx/algorithms/minors/contraction.py +0 -738
  224. package/runtime/python/okstra_vendor/networkx/algorithms/mis.py +0 -78
  225. package/runtime/python/okstra_vendor/networkx/algorithms/moral.py +0 -59
  226. package/runtime/python/okstra_vendor/networkx/algorithms/node_classification.py +0 -219
  227. package/runtime/python/okstra_vendor/networkx/algorithms/non_randomness.py +0 -155
  228. package/runtime/python/okstra_vendor/networkx/algorithms/operators/__init__.py +0 -4
  229. package/runtime/python/okstra_vendor/networkx/algorithms/operators/all.py +0 -324
  230. package/runtime/python/okstra_vendor/networkx/algorithms/operators/binary.py +0 -468
  231. package/runtime/python/okstra_vendor/networkx/algorithms/operators/product.py +0 -633
  232. package/runtime/python/okstra_vendor/networkx/algorithms/operators/unary.py +0 -77
  233. package/runtime/python/okstra_vendor/networkx/algorithms/perfect_graph.py +0 -73
  234. package/runtime/python/okstra_vendor/networkx/algorithms/planar_drawing.py +0 -464
  235. package/runtime/python/okstra_vendor/networkx/algorithms/planarity.py +0 -1463
  236. package/runtime/python/okstra_vendor/networkx/algorithms/polynomials.py +0 -306
  237. package/runtime/python/okstra_vendor/networkx/algorithms/reciprocity.py +0 -98
  238. package/runtime/python/okstra_vendor/networkx/algorithms/regular.py +0 -167
  239. package/runtime/python/okstra_vendor/networkx/algorithms/richclub.py +0 -138
  240. package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/__init__.py +0 -5
  241. package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/astar.py +0 -239
  242. package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/dense.py +0 -264
  243. package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/generic.py +0 -716
  244. package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/unweighted.py +0 -625
  245. package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/weighted.py +0 -2542
  246. package/runtime/python/okstra_vendor/networkx/algorithms/similarity.py +0 -2107
  247. package/runtime/python/okstra_vendor/networkx/algorithms/simple_paths.py +0 -966
  248. package/runtime/python/okstra_vendor/networkx/algorithms/smallworld.py +0 -404
  249. package/runtime/python/okstra_vendor/networkx/algorithms/smetric.py +0 -30
  250. package/runtime/python/okstra_vendor/networkx/algorithms/sparsifiers.py +0 -296
  251. package/runtime/python/okstra_vendor/networkx/algorithms/structuralholes.py +0 -374
  252. package/runtime/python/okstra_vendor/networkx/algorithms/summarization.py +0 -564
  253. package/runtime/python/okstra_vendor/networkx/algorithms/swap.py +0 -406
  254. package/runtime/python/okstra_vendor/networkx/algorithms/threshold.py +0 -981
  255. package/runtime/python/okstra_vendor/networkx/algorithms/time_dependent.py +0 -142
  256. package/runtime/python/okstra_vendor/networkx/algorithms/tournament.py +0 -406
  257. package/runtime/python/okstra_vendor/networkx/algorithms/traversal/__init__.py +0 -5
  258. package/runtime/python/okstra_vendor/networkx/algorithms/traversal/beamsearch.py +0 -90
  259. package/runtime/python/okstra_vendor/networkx/algorithms/traversal/breadth_first_search.py +0 -576
  260. package/runtime/python/okstra_vendor/networkx/algorithms/traversal/depth_first_search.py +0 -529
  261. package/runtime/python/okstra_vendor/networkx/algorithms/traversal/edgebfs.py +0 -185
  262. package/runtime/python/okstra_vendor/networkx/algorithms/traversal/edgedfs.py +0 -182
  263. package/runtime/python/okstra_vendor/networkx/algorithms/tree/__init__.py +0 -7
  264. package/runtime/python/okstra_vendor/networkx/algorithms/tree/branchings.py +0 -1042
  265. package/runtime/python/okstra_vendor/networkx/algorithms/tree/coding.py +0 -413
  266. package/runtime/python/okstra_vendor/networkx/algorithms/tree/decomposition.py +0 -88
  267. package/runtime/python/okstra_vendor/networkx/algorithms/tree/distance_measures.py +0 -219
  268. package/runtime/python/okstra_vendor/networkx/algorithms/tree/mst.py +0 -1281
  269. package/runtime/python/okstra_vendor/networkx/algorithms/tree/operations.py +0 -106
  270. package/runtime/python/okstra_vendor/networkx/algorithms/tree/recognition.py +0 -273
  271. package/runtime/python/okstra_vendor/networkx/algorithms/triads.py +0 -500
  272. package/runtime/python/okstra_vendor/networkx/algorithms/vitality.py +0 -76
  273. package/runtime/python/okstra_vendor/networkx/algorithms/voronoi.py +0 -86
  274. package/runtime/python/okstra_vendor/networkx/algorithms/walks.py +0 -77
  275. package/runtime/python/okstra_vendor/networkx/algorithms/wiener.py +0 -278
  276. package/runtime/python/okstra_vendor/networkx/classes/__init__.py +0 -13
  277. package/runtime/python/okstra_vendor/networkx/classes/coreviews.py +0 -435
  278. package/runtime/python/okstra_vendor/networkx/classes/digraph.py +0 -1363
  279. package/runtime/python/okstra_vendor/networkx/classes/filters.py +0 -95
  280. package/runtime/python/okstra_vendor/networkx/classes/function.py +0 -1549
  281. package/runtime/python/okstra_vendor/networkx/classes/graph.py +0 -2082
  282. package/runtime/python/okstra_vendor/networkx/classes/graphviews.py +0 -269
  283. package/runtime/python/okstra_vendor/networkx/classes/multidigraph.py +0 -977
  284. package/runtime/python/okstra_vendor/networkx/classes/multigraph.py +0 -1294
  285. package/runtime/python/okstra_vendor/networkx/classes/reportviews.py +0 -1447
  286. package/runtime/python/okstra_vendor/networkx/convert.py +0 -502
  287. package/runtime/python/okstra_vendor/networkx/convert_matrix.py +0 -1314
  288. package/runtime/python/okstra_vendor/networkx/drawing/__init__.py +0 -7
  289. package/runtime/python/okstra_vendor/networkx/drawing/layout.py +0 -2036
  290. package/runtime/python/okstra_vendor/networkx/drawing/nx_agraph.py +0 -470
  291. package/runtime/python/okstra_vendor/networkx/drawing/nx_latex.py +0 -570
  292. package/runtime/python/okstra_vendor/networkx/drawing/nx_pydot.py +0 -361
  293. package/runtime/python/okstra_vendor/networkx/drawing/nx_pylab.py +0 -2978
  294. package/runtime/python/okstra_vendor/networkx/exception.py +0 -131
  295. package/runtime/python/okstra_vendor/networkx/generators/__init__.py +0 -34
  296. package/runtime/python/okstra_vendor/networkx/generators/atlas.dat.gz +0 -0
  297. package/runtime/python/okstra_vendor/networkx/generators/atlas.py +0 -227
  298. package/runtime/python/okstra_vendor/networkx/generators/classic.py +0 -1091
  299. package/runtime/python/okstra_vendor/networkx/generators/cographs.py +0 -68
  300. package/runtime/python/okstra_vendor/networkx/generators/community.py +0 -1070
  301. package/runtime/python/okstra_vendor/networkx/generators/degree_seq.py +0 -886
  302. package/runtime/python/okstra_vendor/networkx/generators/directed.py +0 -572
  303. package/runtime/python/okstra_vendor/networkx/generators/duplication.py +0 -174
  304. package/runtime/python/okstra_vendor/networkx/generators/ego.py +0 -66
  305. package/runtime/python/okstra_vendor/networkx/generators/expanders.py +0 -499
  306. package/runtime/python/okstra_vendor/networkx/generators/geometric.py +0 -1037
  307. package/runtime/python/okstra_vendor/networkx/generators/harary_graph.py +0 -163
  308. package/runtime/python/okstra_vendor/networkx/generators/internet_as_graphs.py +0 -443
  309. package/runtime/python/okstra_vendor/networkx/generators/intersection.py +0 -125
  310. package/runtime/python/okstra_vendor/networkx/generators/interval_graph.py +0 -70
  311. package/runtime/python/okstra_vendor/networkx/generators/joint_degree_seq.py +0 -664
  312. package/runtime/python/okstra_vendor/networkx/generators/lattice.py +0 -405
  313. package/runtime/python/okstra_vendor/networkx/generators/line.py +0 -501
  314. package/runtime/python/okstra_vendor/networkx/generators/mycielski.py +0 -110
  315. package/runtime/python/okstra_vendor/networkx/generators/nonisomorphic_trees.py +0 -259
  316. package/runtime/python/okstra_vendor/networkx/generators/random_clustered.py +0 -117
  317. package/runtime/python/okstra_vendor/networkx/generators/random_graphs.py +0 -1416
  318. package/runtime/python/okstra_vendor/networkx/generators/small.py +0 -1070
  319. package/runtime/python/okstra_vendor/networkx/generators/social.py +0 -554
  320. package/runtime/python/okstra_vendor/networkx/generators/spectral_graph_forge.py +0 -120
  321. package/runtime/python/okstra_vendor/networkx/generators/stochastic.py +0 -54
  322. package/runtime/python/okstra_vendor/networkx/generators/sudoku.py +0 -131
  323. package/runtime/python/okstra_vendor/networkx/generators/time_series.py +0 -74
  324. package/runtime/python/okstra_vendor/networkx/generators/trees.py +0 -1070
  325. package/runtime/python/okstra_vendor/networkx/generators/triads.py +0 -94
  326. package/runtime/python/okstra_vendor/networkx/lazy_imports.py +0 -188
  327. package/runtime/python/okstra_vendor/networkx/linalg/__init__.py +0 -13
  328. package/runtime/python/okstra_vendor/networkx/linalg/algebraicconnectivity.py +0 -650
  329. package/runtime/python/okstra_vendor/networkx/linalg/attrmatrix.py +0 -466
  330. package/runtime/python/okstra_vendor/networkx/linalg/bethehessianmatrix.py +0 -77
  331. package/runtime/python/okstra_vendor/networkx/linalg/graphmatrix.py +0 -168
  332. package/runtime/python/okstra_vendor/networkx/linalg/laplacianmatrix.py +0 -512
  333. package/runtime/python/okstra_vendor/networkx/linalg/modularitymatrix.py +0 -166
  334. package/runtime/python/okstra_vendor/networkx/linalg/spectrum.py +0 -186
  335. package/runtime/python/okstra_vendor/networkx/readwrite/__init__.py +0 -17
  336. package/runtime/python/okstra_vendor/networkx/readwrite/adjlist.py +0 -330
  337. package/runtime/python/okstra_vendor/networkx/readwrite/edgelist.py +0 -489
  338. package/runtime/python/okstra_vendor/networkx/readwrite/gexf.py +0 -1084
  339. package/runtime/python/okstra_vendor/networkx/readwrite/gml.py +0 -879
  340. package/runtime/python/okstra_vendor/networkx/readwrite/graph6.py +0 -427
  341. package/runtime/python/okstra_vendor/networkx/readwrite/graphml.py +0 -1053
  342. package/runtime/python/okstra_vendor/networkx/readwrite/json_graph/__init__.py +0 -19
  343. package/runtime/python/okstra_vendor/networkx/readwrite/json_graph/adjacency.py +0 -156
  344. package/runtime/python/okstra_vendor/networkx/readwrite/json_graph/cytoscape.py +0 -190
  345. package/runtime/python/okstra_vendor/networkx/readwrite/json_graph/node_link.py +0 -261
  346. package/runtime/python/okstra_vendor/networkx/readwrite/json_graph/tree.py +0 -137
  347. package/runtime/python/okstra_vendor/networkx/readwrite/leda.py +0 -108
  348. package/runtime/python/okstra_vendor/networkx/readwrite/multiline_adjlist.py +0 -393
  349. package/runtime/python/okstra_vendor/networkx/readwrite/p2g.py +0 -113
  350. package/runtime/python/okstra_vendor/networkx/readwrite/pajek.py +0 -286
  351. package/runtime/python/okstra_vendor/networkx/readwrite/sparse6.py +0 -379
  352. package/runtime/python/okstra_vendor/networkx/readwrite/text.py +0 -851
  353. package/runtime/python/okstra_vendor/networkx/relabel.py +0 -285
  354. package/runtime/python/okstra_vendor/networkx/utils/__init__.py +0 -8
  355. package/runtime/python/okstra_vendor/networkx/utils/backends.py +0 -2171
  356. package/runtime/python/okstra_vendor/networkx/utils/configs.py +0 -396
  357. package/runtime/python/okstra_vendor/networkx/utils/decorators.py +0 -1233
  358. package/runtime/python/okstra_vendor/networkx/utils/heaps.py +0 -338
  359. package/runtime/python/okstra_vendor/networkx/utils/mapped_queue.py +0 -297
  360. package/runtime/python/okstra_vendor/networkx/utils/misc.py +0 -703
  361. package/runtime/python/okstra_vendor/networkx/utils/random_sequence.py +0 -198
  362. package/runtime/python/okstra_vendor/networkx/utils/rcm.py +0 -159
  363. package/runtime/python/okstra_vendor/networkx/utils/union_find.py +0 -106
  364. package/runtime/skills/okstra-graphify/SKILL.md +0 -169
  365. 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