okstra 0.114.0 → 0.116.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 (337) hide show
  1. package/docs/kr/architecture.md +2 -2
  2. package/docs/kr/follow-ups/2026-07-10-final-report-option-3.md +51 -0
  3. package/package.json +1 -1
  4. package/runtime/BUILD.json +2 -2
  5. package/runtime/agents/workers/report-writer-worker.md +1 -0
  6. package/runtime/prompts/launch.template.md +34 -0
  7. package/runtime/prompts/lead/convergence.md +2 -0
  8. package/runtime/prompts/lead/okstra-lead-contract.md +1 -1
  9. package/runtime/prompts/lead/report-writer.md +1 -1
  10. package/runtime/prompts/profiles/implementation-planning.md +1 -0
  11. package/runtime/python/okstra_ctl/implementation_stage.py +46 -39
  12. package/runtime/python/okstra_ctl/incremental_carry.py +70 -0
  13. package/runtime/python/okstra_ctl/incremental_scope.py +89 -0
  14. package/runtime/python/okstra_ctl/report_views.py +151 -12
  15. package/runtime/python/okstra_ctl/run.py +24 -24
  16. package/runtime/python/okstra_ctl/stage_targets.py +26 -0
  17. package/runtime/python/okstra_ctl/worktree.py +1 -1
  18. package/runtime/python/okstra_token_usage/claude.py +58 -19
  19. package/runtime/python/okstra_token_usage/collect.py +3 -1
  20. package/runtime/python/okstra_vendor/__init__.py +3 -0
  21. package/runtime/schemas/final-report-v1.0.schema.json +39 -0
  22. package/runtime/templates/reports/final-report.template.md +27 -0
  23. package/runtime/templates/reports/i18n/en.json +23 -12
  24. package/runtime/templates/reports/i18n/ko.json +23 -12
  25. package/runtime/templates/reports/report.css +55 -0
  26. package/runtime/templates/reports/report.js +53 -1
  27. package/runtime/validators/validate-run.py +50 -0
  28. package/src/cli-registry.mjs +14 -0
  29. package/src/commands/execute/incremental-carry.mjs +20 -0
  30. package/src/commands/execute/incremental-scope.mjs +20 -0
  31. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/__init__.py +0 -0
  32. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_approx_clust_coeff.py +0 -41
  33. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_clique.py +0 -112
  34. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_connectivity.py +0 -199
  35. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_density.py +0 -146
  36. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_distance_measures.py +0 -59
  37. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_dominating_set.py +0 -78
  38. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_kcomponents.py +0 -303
  39. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_matching.py +0 -8
  40. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_maxcut.py +0 -94
  41. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_ramsey.py +0 -31
  42. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_steinertree.py +0 -306
  43. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_traveling_salesman.py +0 -1014
  44. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_treewidth.py +0 -274
  45. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_vertex_cover.py +0 -68
  46. package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/tests/__init__.py +0 -0
  47. package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/tests/base_test.py +0 -81
  48. package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/tests/test_connectivity.py +0 -143
  49. package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/tests/test_correlation.py +0 -122
  50. package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/tests/test_mixing.py +0 -174
  51. package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/tests/test_neighbor_degree.py +0 -107
  52. package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/tests/test_pairs.py +0 -87
  53. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/__init__.py +0 -0
  54. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_basic.py +0 -125
  55. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_centrality.py +0 -192
  56. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_cluster.py +0 -84
  57. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_covering.py +0 -33
  58. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_edgelist.py +0 -240
  59. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_extendability.py +0 -334
  60. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_generators.py +0 -407
  61. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_link_analysis.py +0 -218
  62. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_matching.py +0 -327
  63. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_matrix.py +0 -138
  64. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_project.py +0 -409
  65. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_redundancy.py +0 -35
  66. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_spectral_bipartivity.py +0 -80
  67. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/__init__.py +0 -0
  68. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_betweenness_centrality.py +0 -923
  69. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_betweenness_centrality_subset.py +0 -354
  70. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_closeness_centrality.py +0 -274
  71. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_current_flow_betweenness_centrality.py +0 -259
  72. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_current_flow_betweenness_centrality_subset.py +0 -147
  73. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_current_flow_closeness.py +0 -43
  74. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_degree_centrality.py +0 -144
  75. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_dispersion.py +0 -73
  76. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_eigenvector_centrality.py +0 -186
  77. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_group.py +0 -277
  78. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_harmonic_centrality.py +0 -122
  79. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_katz_centrality.py +0 -345
  80. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_laplacian_centrality.py +0 -220
  81. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_load_centrality.py +0 -344
  82. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_percolation_centrality.py +0 -87
  83. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_reaching.py +0 -140
  84. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_second_order_centrality.py +0 -82
  85. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_subgraph.py +0 -110
  86. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_trophic.py +0 -302
  87. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_voterank.py +0 -64
  88. package/runtime/python/okstra_vendor/networkx/algorithms/coloring/tests/__init__.py +0 -0
  89. package/runtime/python/okstra_vendor/networkx/algorithms/coloring/tests/test_coloring.py +0 -863
  90. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/__init__.py +0 -0
  91. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_asyn_fluid.py +0 -147
  92. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_bipartitions.py +0 -157
  93. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_centrality.py +0 -85
  94. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_divisive.py +0 -106
  95. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_kclique.py +0 -91
  96. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_label_propagation.py +0 -241
  97. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_leiden.py +0 -138
  98. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_local.py +0 -76
  99. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_louvain.py +0 -264
  100. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_lukes.py +0 -152
  101. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_modularity_max.py +0 -340
  102. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_quality.py +0 -139
  103. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_utils.py +0 -26
  104. package/runtime/python/okstra_vendor/networkx/algorithms/components/tests/__init__.py +0 -0
  105. package/runtime/python/okstra_vendor/networkx/algorithms/components/tests/test_attracting.py +0 -70
  106. package/runtime/python/okstra_vendor/networkx/algorithms/components/tests/test_biconnected.py +0 -248
  107. package/runtime/python/okstra_vendor/networkx/algorithms/components/tests/test_connected.py +0 -138
  108. package/runtime/python/okstra_vendor/networkx/algorithms/components/tests/test_semiconnected.py +0 -55
  109. package/runtime/python/okstra_vendor/networkx/algorithms/components/tests/test_strongly_connected.py +0 -193
  110. package/runtime/python/okstra_vendor/networkx/algorithms/components/tests/test_weakly_connected.py +0 -96
  111. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/tests/__init__.py +0 -0
  112. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/tests/test_connectivity.py +0 -421
  113. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/tests/test_cuts.py +0 -309
  114. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/tests/test_disjoint_paths.py +0 -249
  115. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/tests/test_edge_augmentation.py +0 -502
  116. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/tests/test_edge_kcomponents.py +0 -488
  117. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/tests/test_kcomponents.py +0 -323
  118. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/tests/test_kcutsets.py +0 -280
  119. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/tests/test_stoer_wagner.py +0 -102
  120. package/runtime/python/okstra_vendor/networkx/algorithms/flow/tests/__init__.py +0 -0
  121. package/runtime/python/okstra_vendor/networkx/algorithms/flow/tests/gl1.gpickle.bz2 +0 -0
  122. package/runtime/python/okstra_vendor/networkx/algorithms/flow/tests/gw1.gpickle.bz2 +0 -0
  123. package/runtime/python/okstra_vendor/networkx/algorithms/flow/tests/netgen-2.gpickle.bz2 +0 -0
  124. package/runtime/python/okstra_vendor/networkx/algorithms/flow/tests/test_gomory_hu.py +0 -128
  125. package/runtime/python/okstra_vendor/networkx/algorithms/flow/tests/test_maxflow.py +0 -573
  126. package/runtime/python/okstra_vendor/networkx/algorithms/flow/tests/test_maxflow_large_graph.py +0 -155
  127. package/runtime/python/okstra_vendor/networkx/algorithms/flow/tests/test_mincost.py +0 -475
  128. package/runtime/python/okstra_vendor/networkx/algorithms/flow/tests/test_networksimplex.py +0 -481
  129. package/runtime/python/okstra_vendor/networkx/algorithms/flow/tests/wlm3.gpickle.bz2 +0 -0
  130. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/__init__.py +0 -0
  131. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/iso_r01_s80.A99 +0 -0
  132. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/iso_r01_s80.B99 +0 -0
  133. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/si2_b06_m200.A99 +0 -0
  134. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/si2_b06_m200.B99 +0 -0
  135. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/test_ismags.py +0 -719
  136. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/test_isomorphism.py +0 -103
  137. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/test_isomorphvf2.py +0 -490
  138. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/test_match_helpers.py +0 -64
  139. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/test_temporalisomorphvf2.py +0 -212
  140. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/test_tree_isomorphism.py +0 -202
  141. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/test_vf2pp.py +0 -1655
  142. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/test_vf2pp_helpers.py +0 -3118
  143. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/test_vf2userfunc.py +0 -196
  144. package/runtime/python/okstra_vendor/networkx/algorithms/link_analysis/tests/__init__.py +0 -0
  145. package/runtime/python/okstra_vendor/networkx/algorithms/link_analysis/tests/test_hits.py +0 -77
  146. package/runtime/python/okstra_vendor/networkx/algorithms/link_analysis/tests/test_pagerank.py +0 -213
  147. package/runtime/python/okstra_vendor/networkx/algorithms/minors/tests/test_contraction.py +0 -544
  148. package/runtime/python/okstra_vendor/networkx/algorithms/operators/tests/__init__.py +0 -0
  149. package/runtime/python/okstra_vendor/networkx/algorithms/operators/tests/test_all.py +0 -328
  150. package/runtime/python/okstra_vendor/networkx/algorithms/operators/tests/test_binary.py +0 -451
  151. package/runtime/python/okstra_vendor/networkx/algorithms/operators/tests/test_product.py +0 -491
  152. package/runtime/python/okstra_vendor/networkx/algorithms/operators/tests/test_unary.py +0 -55
  153. package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/tests/__init__.py +0 -0
  154. package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/tests/test_astar.py +0 -254
  155. package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/tests/test_dense.py +0 -212
  156. package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/tests/test_dense_numpy.py +0 -88
  157. package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/tests/test_generic.py +0 -511
  158. package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/tests/test_unweighted.py +0 -149
  159. package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/tests/test_weighted.py +0 -983
  160. package/runtime/python/okstra_vendor/networkx/algorithms/tests/__init__.py +0 -0
  161. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_asteroidal.py +0 -23
  162. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_boundary.py +0 -154
  163. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_bridges.py +0 -144
  164. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_broadcasting.py +0 -109
  165. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_chains.py +0 -136
  166. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_chordal.py +0 -129
  167. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_clique.py +0 -300
  168. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_cluster.py +0 -678
  169. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_communicability.py +0 -80
  170. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_core.py +0 -266
  171. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_covering.py +0 -85
  172. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_cuts.py +0 -171
  173. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_cycles.py +0 -984
  174. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_d_separation.py +0 -340
  175. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_dag.py +0 -835
  176. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_distance_measures.py +0 -831
  177. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_distance_regular.py +0 -85
  178. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_dominance.py +0 -299
  179. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_dominating.py +0 -115
  180. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_efficiency.py +0 -58
  181. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_euler.py +0 -314
  182. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_graph_hashing.py +0 -872
  183. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_graphical.py +0 -163
  184. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_hierarchy.py +0 -46
  185. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_hybrid.py +0 -24
  186. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_isolate.py +0 -26
  187. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_link_prediction.py +0 -615
  188. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_lowest_common_ancestors.py +0 -459
  189. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_matching.py +0 -556
  190. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_max_weight_clique.py +0 -179
  191. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_mis.py +0 -62
  192. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_moral.py +0 -15
  193. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_node_classification.py +0 -140
  194. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_non_randomness.py +0 -60
  195. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_perfect_graph.py +0 -27
  196. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_planar_drawing.py +0 -274
  197. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_planarity.py +0 -556
  198. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_polynomials.py +0 -57
  199. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_reciprocity.py +0 -37
  200. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_regular.py +0 -88
  201. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_richclub.py +0 -149
  202. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_similarity.py +0 -1158
  203. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_simple_paths.py +0 -803
  204. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_smallworld.py +0 -76
  205. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_smetric.py +0 -8
  206. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_sparsifiers.py +0 -138
  207. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_structuralholes.py +0 -191
  208. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_summarization.py +0 -642
  209. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_swap.py +0 -179
  210. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_threshold.py +0 -270
  211. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_time_dependent.py +0 -431
  212. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_tournament.py +0 -161
  213. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_triads.py +0 -248
  214. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_vitality.py +0 -41
  215. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_voronoi.py +0 -103
  216. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_walks.py +0 -54
  217. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_wiener.py +0 -157
  218. package/runtime/python/okstra_vendor/networkx/algorithms/traversal/tests/__init__.py +0 -0
  219. package/runtime/python/okstra_vendor/networkx/algorithms/traversal/tests/test_beamsearch.py +0 -25
  220. package/runtime/python/okstra_vendor/networkx/algorithms/traversal/tests/test_bfs.py +0 -203
  221. package/runtime/python/okstra_vendor/networkx/algorithms/traversal/tests/test_dfs.py +0 -307
  222. package/runtime/python/okstra_vendor/networkx/algorithms/traversal/tests/test_edgebfs.py +0 -147
  223. package/runtime/python/okstra_vendor/networkx/algorithms/traversal/tests/test_edgedfs.py +0 -131
  224. package/runtime/python/okstra_vendor/networkx/algorithms/tree/tests/__init__.py +0 -0
  225. package/runtime/python/okstra_vendor/networkx/algorithms/tree/tests/test_branchings.py +0 -624
  226. package/runtime/python/okstra_vendor/networkx/algorithms/tree/tests/test_coding.py +0 -114
  227. package/runtime/python/okstra_vendor/networkx/algorithms/tree/tests/test_decomposition.py +0 -79
  228. package/runtime/python/okstra_vendor/networkx/algorithms/tree/tests/test_distance_measures.py +0 -99
  229. package/runtime/python/okstra_vendor/networkx/algorithms/tree/tests/test_mst.py +0 -934
  230. package/runtime/python/okstra_vendor/networkx/algorithms/tree/tests/test_operations.py +0 -53
  231. package/runtime/python/okstra_vendor/networkx/algorithms/tree/tests/test_recognition.py +0 -174
  232. package/runtime/python/okstra_vendor/networkx/classes/tests/__init__.py +0 -0
  233. package/runtime/python/okstra_vendor/networkx/classes/tests/dispatch_interface.py +0 -192
  234. package/runtime/python/okstra_vendor/networkx/classes/tests/historical_tests.py +0 -476
  235. package/runtime/python/okstra_vendor/networkx/classes/tests/test_coreviews.py +0 -362
  236. package/runtime/python/okstra_vendor/networkx/classes/tests/test_digraph.py +0 -331
  237. package/runtime/python/okstra_vendor/networkx/classes/tests/test_digraph_historical.py +0 -110
  238. package/runtime/python/okstra_vendor/networkx/classes/tests/test_filters.py +0 -177
  239. package/runtime/python/okstra_vendor/networkx/classes/tests/test_function.py +0 -1045
  240. package/runtime/python/okstra_vendor/networkx/classes/tests/test_graph.py +0 -950
  241. package/runtime/python/okstra_vendor/networkx/classes/tests/test_graph_historical.py +0 -12
  242. package/runtime/python/okstra_vendor/networkx/classes/tests/test_graphviews.py +0 -349
  243. package/runtime/python/okstra_vendor/networkx/classes/tests/test_multidigraph.py +0 -459
  244. package/runtime/python/okstra_vendor/networkx/classes/tests/test_multigraph.py +0 -528
  245. package/runtime/python/okstra_vendor/networkx/classes/tests/test_reportviews.py +0 -1421
  246. package/runtime/python/okstra_vendor/networkx/classes/tests/test_special.py +0 -131
  247. package/runtime/python/okstra_vendor/networkx/classes/tests/test_subgraphviews.py +0 -371
  248. package/runtime/python/okstra_vendor/networkx/conftest.py +0 -261
  249. package/runtime/python/okstra_vendor/networkx/drawing/tests/__init__.py +0 -0
  250. package/runtime/python/okstra_vendor/networkx/drawing/tests/baseline/test_display_complex.png +0 -0
  251. package/runtime/python/okstra_vendor/networkx/drawing/tests/baseline/test_display_empty_graph.png +0 -0
  252. package/runtime/python/okstra_vendor/networkx/drawing/tests/baseline/test_display_house_with_colors.png +0 -0
  253. package/runtime/python/okstra_vendor/networkx/drawing/tests/baseline/test_display_labels_and_colors.png +0 -0
  254. package/runtime/python/okstra_vendor/networkx/drawing/tests/baseline/test_display_shortest_path.png +0 -0
  255. package/runtime/python/okstra_vendor/networkx/drawing/tests/baseline/test_house_with_colors.png +0 -0
  256. package/runtime/python/okstra_vendor/networkx/drawing/tests/test_agraph.py +0 -237
  257. package/runtime/python/okstra_vendor/networkx/drawing/tests/test_image_comparison_pylab_mpl.py +0 -229
  258. package/runtime/python/okstra_vendor/networkx/drawing/tests/test_latex.py +0 -285
  259. package/runtime/python/okstra_vendor/networkx/drawing/tests/test_layout.py +0 -631
  260. package/runtime/python/okstra_vendor/networkx/drawing/tests/test_pydot.py +0 -146
  261. package/runtime/python/okstra_vendor/networkx/drawing/tests/test_pylab.py +0 -1582
  262. package/runtime/python/okstra_vendor/networkx/generators/tests/__init__.py +0 -0
  263. package/runtime/python/okstra_vendor/networkx/generators/tests/test_atlas.py +0 -75
  264. package/runtime/python/okstra_vendor/networkx/generators/tests/test_classic.py +0 -642
  265. package/runtime/python/okstra_vendor/networkx/generators/tests/test_cographs.py +0 -20
  266. package/runtime/python/okstra_vendor/networkx/generators/tests/test_community.py +0 -362
  267. package/runtime/python/okstra_vendor/networkx/generators/tests/test_degree_seq.py +0 -224
  268. package/runtime/python/okstra_vendor/networkx/generators/tests/test_directed.py +0 -189
  269. package/runtime/python/okstra_vendor/networkx/generators/tests/test_duplication.py +0 -103
  270. package/runtime/python/okstra_vendor/networkx/generators/tests/test_ego.py +0 -39
  271. package/runtime/python/okstra_vendor/networkx/generators/tests/test_expanders.py +0 -182
  272. package/runtime/python/okstra_vendor/networkx/generators/tests/test_geometric.py +0 -488
  273. package/runtime/python/okstra_vendor/networkx/generators/tests/test_harary_graph.py +0 -133
  274. package/runtime/python/okstra_vendor/networkx/generators/tests/test_internet_as_graphs.py +0 -221
  275. package/runtime/python/okstra_vendor/networkx/generators/tests/test_intersection.py +0 -28
  276. package/runtime/python/okstra_vendor/networkx/generators/tests/test_interval_graph.py +0 -144
  277. package/runtime/python/okstra_vendor/networkx/generators/tests/test_joint_degree_seq.py +0 -125
  278. package/runtime/python/okstra_vendor/networkx/generators/tests/test_lattice.py +0 -264
  279. package/runtime/python/okstra_vendor/networkx/generators/tests/test_line.py +0 -316
  280. package/runtime/python/okstra_vendor/networkx/generators/tests/test_mycielski.py +0 -30
  281. package/runtime/python/okstra_vendor/networkx/generators/tests/test_nonisomorphic_trees.py +0 -82
  282. package/runtime/python/okstra_vendor/networkx/generators/tests/test_random_clustered.py +0 -33
  283. package/runtime/python/okstra_vendor/networkx/generators/tests/test_random_graphs.py +0 -495
  284. package/runtime/python/okstra_vendor/networkx/generators/tests/test_small.py +0 -220
  285. package/runtime/python/okstra_vendor/networkx/generators/tests/test_spectral_graph_forge.py +0 -49
  286. package/runtime/python/okstra_vendor/networkx/generators/tests/test_stochastic.py +0 -72
  287. package/runtime/python/okstra_vendor/networkx/generators/tests/test_sudoku.py +0 -92
  288. package/runtime/python/okstra_vendor/networkx/generators/tests/test_time_series.py +0 -64
  289. package/runtime/python/okstra_vendor/networkx/generators/tests/test_trees.py +0 -195
  290. package/runtime/python/okstra_vendor/networkx/generators/tests/test_triads.py +0 -15
  291. package/runtime/python/okstra_vendor/networkx/linalg/tests/__init__.py +0 -0
  292. package/runtime/python/okstra_vendor/networkx/linalg/tests/test_algebraic_connectivity.py +0 -400
  293. package/runtime/python/okstra_vendor/networkx/linalg/tests/test_attrmatrix.py +0 -108
  294. package/runtime/python/okstra_vendor/networkx/linalg/tests/test_bethehessian.py +0 -40
  295. package/runtime/python/okstra_vendor/networkx/linalg/tests/test_graphmatrix.py +0 -275
  296. package/runtime/python/okstra_vendor/networkx/linalg/tests/test_laplacian.py +0 -334
  297. package/runtime/python/okstra_vendor/networkx/linalg/tests/test_modularity.py +0 -86
  298. package/runtime/python/okstra_vendor/networkx/linalg/tests/test_spectrum.py +0 -70
  299. package/runtime/python/okstra_vendor/networkx/readwrite/json_graph/tests/__init__.py +0 -0
  300. package/runtime/python/okstra_vendor/networkx/readwrite/json_graph/tests/test_adjacency.py +0 -78
  301. package/runtime/python/okstra_vendor/networkx/readwrite/json_graph/tests/test_cytoscape.py +0 -78
  302. package/runtime/python/okstra_vendor/networkx/readwrite/json_graph/tests/test_node_link.py +0 -109
  303. package/runtime/python/okstra_vendor/networkx/readwrite/json_graph/tests/test_tree.py +0 -48
  304. package/runtime/python/okstra_vendor/networkx/readwrite/tests/__init__.py +0 -0
  305. package/runtime/python/okstra_vendor/networkx/readwrite/tests/test_adjlist.py +0 -354
  306. package/runtime/python/okstra_vendor/networkx/readwrite/tests/test_edgelist.py +0 -318
  307. package/runtime/python/okstra_vendor/networkx/readwrite/tests/test_gexf.py +0 -612
  308. package/runtime/python/okstra_vendor/networkx/readwrite/tests/test_gml.py +0 -744
  309. package/runtime/python/okstra_vendor/networkx/readwrite/tests/test_graph6.py +0 -181
  310. package/runtime/python/okstra_vendor/networkx/readwrite/tests/test_graphml.py +0 -1531
  311. package/runtime/python/okstra_vendor/networkx/readwrite/tests/test_leda.py +0 -30
  312. package/runtime/python/okstra_vendor/networkx/readwrite/tests/test_p2g.py +0 -63
  313. package/runtime/python/okstra_vendor/networkx/readwrite/tests/test_pajek.py +0 -128
  314. package/runtime/python/okstra_vendor/networkx/readwrite/tests/test_sparse6.py +0 -166
  315. package/runtime/python/okstra_vendor/networkx/readwrite/tests/test_text.py +0 -1742
  316. package/runtime/python/okstra_vendor/networkx/tests/__init__.py +0 -0
  317. package/runtime/python/okstra_vendor/networkx/tests/test_all_random_functions.py +0 -248
  318. package/runtime/python/okstra_vendor/networkx/tests/test_convert.py +0 -321
  319. package/runtime/python/okstra_vendor/networkx/tests/test_convert_numpy.py +0 -531
  320. package/runtime/python/okstra_vendor/networkx/tests/test_convert_pandas.py +0 -349
  321. package/runtime/python/okstra_vendor/networkx/tests/test_convert_scipy.py +0 -281
  322. package/runtime/python/okstra_vendor/networkx/tests/test_exceptions.py +0 -40
  323. package/runtime/python/okstra_vendor/networkx/tests/test_import.py +0 -11
  324. package/runtime/python/okstra_vendor/networkx/tests/test_lazy_imports.py +0 -96
  325. package/runtime/python/okstra_vendor/networkx/tests/test_relabel.py +0 -349
  326. package/runtime/python/okstra_vendor/networkx/tests/test_removed_functions_exception_messages.py +0 -8
  327. package/runtime/python/okstra_vendor/networkx/utils/tests/__init__.py +0 -0
  328. package/runtime/python/okstra_vendor/networkx/utils/tests/test__init.py +0 -11
  329. package/runtime/python/okstra_vendor/networkx/utils/tests/test_backends.py +0 -225
  330. package/runtime/python/okstra_vendor/networkx/utils/tests/test_config.py +0 -263
  331. package/runtime/python/okstra_vendor/networkx/utils/tests/test_decorators.py +0 -510
  332. package/runtime/python/okstra_vendor/networkx/utils/tests/test_heaps.py +0 -131
  333. package/runtime/python/okstra_vendor/networkx/utils/tests/test_mapped_queue.py +0 -268
  334. package/runtime/python/okstra_vendor/networkx/utils/tests/test_misc.py +0 -393
  335. package/runtime/python/okstra_vendor/networkx/utils/tests/test_random_sequence.py +0 -53
  336. package/runtime/python/okstra_vendor/networkx/utils/tests/test_rcm.py +0 -63
  337. package/runtime/python/okstra_vendor/networkx/utils/tests/test_unionfind.py +0 -55
@@ -67,6 +67,7 @@ def _strip_leading_frontmatter(text: str) -> str:
67
67
 
68
68
  from .clarification_items import (
69
69
  _CELL_ANCHOR_RE,
70
+ UNRESOLVED_STATUSES,
70
71
  _section_1_slice,
71
72
  _split_pipe_row,
72
73
  parse_clarification_items,
@@ -117,6 +118,19 @@ class ReportViewModel:
117
118
  sidecar_name: str
118
119
  sidecar_dir: str
119
120
  approval_ctx: PlanApprovalContext | None = None
121
+ reader_ctx: ReaderDashboardContext | None = None
122
+
123
+
124
+ @dataclass(frozen=True)
125
+ class ReaderDashboardContext:
126
+ decision: str
127
+ human_action_required: str
128
+ blocking_items: str
129
+ safe_to_skip: str
130
+ recommended_command: str
131
+ open_clarifications: int
132
+ approval: str
133
+ recommended_option: str
120
134
 
121
135
 
122
136
  def build_report_view_model(
@@ -124,6 +138,7 @@ def build_report_view_model(
124
138
  *,
125
139
  run_meta: RunMeta,
126
140
  approval_ctx: PlanApprovalContext | None = None,
141
+ reader_ctx: ReaderDashboardContext | None = None,
127
142
  ) -> ReportViewModel:
128
143
  digest = source_digest(src_md)
129
144
  body_md = _strip_leading_frontmatter(src_md)
@@ -141,6 +156,36 @@ def build_report_view_model(
141
156
  sidecar_name=f"user-response-{run_meta.task_type}-{run_meta.seq}.md",
142
157
  sidecar_dir=f"runs/{run_meta.task_type}/user-responses/",
143
158
  approval_ctx=approval_ctx,
159
+ reader_ctx=reader_ctx,
160
+ )
161
+
162
+
163
+ def _reader_dashboard(ctx: ReaderDashboardContext | None) -> str:
164
+ if ctx is None:
165
+ return ""
166
+ rows = (
167
+ ("Decision", html.escape(ctx.decision)),
168
+ ("Human action required", html.escape(ctx.human_action_required)),
169
+ ("Blocking items", html.escape(ctx.blocking_items)),
170
+ ("Safe to skip", html.escape(ctx.safe_to_skip)),
171
+ ("Open clarifications", str(ctx.open_clarifications)),
172
+ ("Approval", html.escape(ctx.approval)),
173
+ ("Recommended option", html.escape(ctx.recommended_option or "--")),
174
+ ("Recommended command", f"<code>{html.escape(ctx.recommended_command or '--')}</code>"),
175
+ )
176
+ summary = "".join(f"<dt>{label}</dt><dd>{value}</dd>" for label, value in rows)
177
+ return (
178
+ '<section class="reader-dashboard" aria-label="Reader summary" data-reader-section="action">\n'
179
+ ' <div class="reader-dashboard-head">\n'
180
+ " <h2>Reader Summary</h2>\n"
181
+ ' <div class="reader-mode-controls" aria-label="Reading mode">\n'
182
+ ' <button type="button" data-action="set-reader-mode" data-reader-mode="action" aria-pressed="true">Action</button>\n'
183
+ ' <button type="button" data-action="set-reader-mode" data-reader-mode="audit" aria-pressed="false">Audit</button>\n'
184
+ ' <button type="button" data-action="set-reader-mode" data-reader-mode="full" aria-pressed="false">Full</button>\n'
185
+ " </div>\n"
186
+ " </div>\n"
187
+ f' <dl class="reader-summary-grid">{summary}</dl>\n'
188
+ "</section>\n"
144
189
  )
145
190
 
146
191
 
@@ -171,7 +216,7 @@ def render_report_view_model(
171
216
  f" <div>{title}</div>\n"
172
217
  f" <button type=\"button\" data-action=\"export-user-response\">Export user response</button>\n"
173
218
  f"</header>\n"
174
- f"<main>{model.body_html}</main>\n"
219
+ f"<main>{_reader_dashboard(model.reader_ctx)}{model.body_html}</main>\n"
175
220
  f"{_plan_approval_section(model.approval_ctx, run_meta) if model.approval_ctx else ''}"
176
221
  f"<footer class=\"report-footer\">\n"
177
222
  f" <button type=\"button\" data-action=\"export-user-response\">Export user response</button>\n"
@@ -202,6 +247,7 @@ def render_html(
202
247
  css: str,
203
248
  js: str,
204
249
  approval_ctx: PlanApprovalContext | None = None,
250
+ reader_ctx: ReaderDashboardContext | None = None,
205
251
  ) -> str:
206
252
  """Return a single self-contained HTML document for ``src_md``.
207
253
 
@@ -213,6 +259,7 @@ def render_html(
213
259
  src_md,
214
260
  run_meta=run_meta,
215
261
  approval_ctx=approval_ctx,
262
+ reader_ctx=reader_ctx,
216
263
  )
217
264
  return render_report_view_model(model, css=css, js=js)
218
265
 
@@ -948,6 +995,17 @@ class PlanApprovalContext:
948
995
  blocker_ids: tuple[str, ...]
949
996
 
950
997
 
998
+ def _load_report_data(src_md_path: Path) -> dict | None:
999
+ data_path = final_report_data_path(src_md_path)
1000
+ if not data_path.is_file():
1001
+ return None
1002
+ try:
1003
+ data = json.loads(data_path.read_text(encoding="utf-8"))
1004
+ except (OSError, json.JSONDecodeError):
1005
+ return None
1006
+ return data if isinstance(data, dict) else None
1007
+
1008
+
951
1009
  def plan_approval_context(
952
1010
  src_md_path: Path, src_text: str
953
1011
  ) -> PlanApprovalContext | None:
@@ -957,12 +1015,8 @@ def plan_approval_context(
957
1015
  validator 가 같은 판정을 공유한다. recommendedOption 이 비거나 후보에 없으면
958
1016
  첫 후보로 폴백한다 — 항상 정확히 한 옵션이 selected 가 되어 브라우저 자동선택분의
959
1017
  묵시 Export 를 차단한다."""
960
- data_path = final_report_data_path(src_md_path)
961
- if not data_path.is_file():
962
- return None
963
- try:
964
- data = json.loads(data_path.read_text(encoding="utf-8"))
965
- except (OSError, json.JSONDecodeError):
1018
+ data = _load_report_data(src_md_path)
1019
+ if data is None:
966
1020
  return None
967
1021
  planning = data.get("implementationPlanning")
968
1022
  if not isinstance(planning, dict):
@@ -994,6 +1048,83 @@ def plan_approval_context(
994
1048
  )
995
1049
 
996
1050
 
1051
+ def reader_dashboard_context(
1052
+ src_md_path: Path,
1053
+ src_text: str,
1054
+ approval_ctx: PlanApprovalContext | None,
1055
+ ) -> ReaderDashboardContext | None:
1056
+ data = _load_report_data(src_md_path)
1057
+ if data is None:
1058
+ return None
1059
+ reader = data.get("readerSummary")
1060
+ if not isinstance(reader, dict):
1061
+ reader = {}
1062
+ verdict = data.get("verdictCard")
1063
+ if not isinstance(verdict, dict):
1064
+ verdict = {}
1065
+ clarifications = parse_clarification_items(src_text) or []
1066
+ open_count = sum(
1067
+ 1 for item in clarifications
1068
+ if re.fullmatch(r"C-\d+", item.row_id) and item.status in UNRESOLVED_STATUSES
1069
+ )
1070
+ recommended_option = ""
1071
+ planning = data.get("implementationPlanning")
1072
+ if isinstance(planning, dict):
1073
+ rec = planning.get("recommendedOption")
1074
+ recommended_option = rec.get("name") or "" if isinstance(rec, dict) else ""
1075
+ if approval_ctx is not None:
1076
+ recommended_option = approval_ctx.recommended_option
1077
+ return ReaderDashboardContext(
1078
+ decision=_reader_text(reader.get("decision")) or _reader_text(verdict.get("finalConclusion")),
1079
+ human_action_required=(
1080
+ _reader_text(reader.get("humanActionRequired"))
1081
+ or _reader_text(verdict.get("nextStep"))
1082
+ or "Review the report sections below."
1083
+ ),
1084
+ blocking_items=(
1085
+ _reader_text(reader.get("blockingItems"))
1086
+ or _fallback_blocking_items(open_count, approval_ctx)
1087
+ ),
1088
+ safe_to_skip=(
1089
+ _reader_text(reader.get("safeToSkip"))
1090
+ or "Audit sections can wait until a deeper review."
1091
+ ),
1092
+ recommended_command=(
1093
+ _reader_text(reader.get("recommendedCommand"))
1094
+ or _reader_text(verdict.get("nextStep"))
1095
+ ),
1096
+ open_clarifications=open_count,
1097
+ approval=_approval_label(data, approval_ctx),
1098
+ recommended_option=recommended_option,
1099
+ )
1100
+
1101
+
1102
+ def _reader_text(value: object) -> str:
1103
+ return value.strip() if isinstance(value, str) else ""
1104
+
1105
+
1106
+ def _fallback_blocking_items(
1107
+ open_count: int, approval_ctx: PlanApprovalContext | None
1108
+ ) -> str:
1109
+ if approval_ctx is not None and approval_ctx.blocker_ids:
1110
+ return ", ".join(approval_ctx.blocker_ids)
1111
+ if open_count:
1112
+ return f"{open_count} open clarification item(s)."
1113
+ return "No open clarification items."
1114
+
1115
+
1116
+ def _approval_label(data: dict, approval_ctx: PlanApprovalContext | None) -> str:
1117
+ frontmatter = data.get("frontmatter")
1118
+ approved = frontmatter.get("approved") if isinstance(frontmatter, dict) else False
1119
+ if approved is True:
1120
+ return "approved"
1121
+ if approval_ctx is not None:
1122
+ return "blocked" if approval_ctx.disabled_reason else "ready"
1123
+ verdict = data.get("verdictCard")
1124
+ required = verdict.get("approvalRequired") if isinstance(verdict, dict) else False
1125
+ return "required" if required else "not required"
1126
+
1127
+
997
1128
  def _resolve_recommended_option(rec_name: str, names: tuple[str, ...]) -> str:
998
1129
  """``recommendedOption.name`` 을 후보 이름에 맞춘다. 정확 일치가 없으면
999
1130
  접두 일치(후보가 ``(RECOMMENDED)`` 같은 표식 접미를 더 달고 있는 흔한 경우)를
@@ -1095,15 +1226,23 @@ def render_html_view(
1095
1226
  "the HTML view."
1096
1227
  )
1097
1228
  approval_ctx = plan_approval_context(src_md_path, src_text)
1098
- if not report_has_clarification_items(src_text) and approval_ctx is None:
1229
+ reader_ctx = reader_dashboard_context(src_md_path, src_text, approval_ctx)
1230
+ has_clarifications = report_has_clarification_items(src_text)
1231
+ if not has_clarifications and approval_ctx is None and reader_ctx is None:
1099
1232
  if html_path.is_file():
1100
1233
  html_path.unlink()
1101
1234
  return None
1102
1235
  html_text = render_html(
1103
- src_text, run_meta=run_meta, css=css, js=js, approval_ctx=approval_ctx
1236
+ src_text,
1237
+ run_meta=run_meta,
1238
+ css=css,
1239
+ js=js,
1240
+ approval_ctx=approval_ctx,
1241
+ reader_ctx=reader_ctx,
1104
1242
  )
1105
1243
  html_path.write_text(html_text, encoding="utf-8")
1106
- # 사용자 확인(폼/승인)이 필요한 보고서 Export 파일의 저장 위치를 미리
1107
- # 만들어 사용자가 디렉토리를 만들 필요가 없게 한다 (reports/ sibling).
1108
- user_responses_dir_for_report(src_md_path).mkdir(parents=True, exist_ok=True)
1244
+ if has_clarifications or approval_ctx is not None:
1245
+ # 사용자 확인(폼/승인)이 필요한 보고서 Export 파일의 저장 위치를 미리
1246
+ # 만들어 사용자가 디렉토리를 만들 필요가 없게 한다 (reports/ 의 sibling).
1247
+ user_responses_dir_for_report(src_md_path).mkdir(parents=True, exist_ok=True)
1109
1248
  return html_path
@@ -1412,7 +1412,7 @@ def _resolve_model_bindings(inp: PrepareInputs, workers: list[str]) -> _ModelBin
1412
1412
  )
1413
1413
 
1414
1414
 
1415
- StageSelection = _implementation_stage.StageSelection
1415
+ StageRunClaim = _implementation_stage.StageRunClaim
1416
1416
 
1417
1417
 
1418
1418
  def _implementation_stage_prepare_error(
@@ -1421,17 +1421,17 @@ def _implementation_stage_prepare_error(
1421
1421
  return PrepareError(str(exc))
1422
1422
 
1423
1423
 
1424
- def _select_and_provision_implementation_stage(
1424
+ def _claim_implementation_stage_run(
1425
1425
  inp: PrepareInputs,
1426
1426
  ctx_stage_map: list,
1427
1427
  task_group_segment: str,
1428
1428
  task_id_segment: str,
1429
1429
  task_key: str,
1430
1430
  executor_worktree_status: str,
1431
- ) -> StageSelection:
1432
- """Compatibility wrapper for implementation stage selection."""
1431
+ ) -> StageRunClaim:
1432
+ """Compatibility wrapper for claiming an implementation stage run."""
1433
1433
  try:
1434
- return _implementation_stage.select_and_provision_implementation_stage(
1434
+ return _implementation_stage.claim_implementation_stage_run(
1435
1435
  inp,
1436
1436
  ctx_stage_map,
1437
1437
  task_group_segment,
@@ -1443,14 +1443,14 @@ def _select_and_provision_implementation_stage(
1443
1443
  raise _implementation_stage_prepare_error(exc) from exc
1444
1444
 
1445
1445
 
1446
- def _apply_implementation_stage(
1446
+ def _publish_stage_run_claim(
1447
1447
  inp: PrepareInputs,
1448
1448
  ctx: dict,
1449
1449
  ctx_stage_map: list,
1450
- sel: StageSelection,
1450
+ claim: StageRunClaim,
1451
1451
  ) -> None:
1452
- """Compatibility wrapper for publishing implementation stage selection."""
1453
- _implementation_stage.apply_implementation_stage(inp, ctx, ctx_stage_map, sel)
1452
+ """Compatibility wrapper for publishing a Stage Run Claim."""
1453
+ _implementation_stage.publish_stage_run_claim(inp, ctx, ctx_stage_map, claim)
1454
1454
 
1455
1455
 
1456
1456
  def _git_out(cwd, *args) -> str:
@@ -1898,7 +1898,7 @@ def prepare_task_bundle(inp: PrepareInputs) -> PrepareOutputs:
1898
1898
  # Identity segments derived with the SAME slugify rule compute_run_paths
1899
1899
  # uses, so they match ctx["TASK_GROUP_SEGMENT"]/["TASK_ID_SEGMENT"]
1900
1900
  # byte-for-byte. We need them BEFORE run-path compute because
1901
- # implementation stage selection depends on the task-worktree degrade
1901
+ # implementation Stage Run Claim depends on the task-worktree degrade
1902
1902
  # status, and the run path itself is stage-namespaced.
1903
1903
  task_group_segment = slugify(inp.task_group)
1904
1904
  task_id_segment = slugify(inp.task_id)
@@ -1908,7 +1908,7 @@ def prepare_task_bundle(inp: PrepareInputs) -> PrepareOutputs:
1908
1908
  # One worktree per task-key: requirements-discovery, error-analysis,
1909
1909
  # implementation-planning and implementation phases of the same task
1910
1910
  # all share this directory and branch. Runs BEFORE run-path compute: its
1911
- # degrade status (skipped-*) feeds implementation stage selection, and
1911
+ # degrade status (skipped-*) feeds implementation Stage Run Claim, and
1912
1912
  # the resolved stage namespaces the run path.
1913
1913
  #
1914
1914
  # The whole check→select→`git worktree add`→reserve sequence (task
@@ -1944,26 +1944,26 @@ def prepare_task_bundle(inp: PrepareInputs) -> PrepareOutputs:
1944
1944
  f"task worktree provisioning failed: {exc}"
1945
1945
  ) from exc
1946
1946
 
1947
- # ---- implementation stage selection (path-independent) ----
1947
+ # ---- implementation Stage Run Claim (path-independent) ----
1948
1948
  # Resolve + provision the stage BEFORE run-path compute so RUN_DIR
1949
1949
  # lands in runs/implementation/stage-<N>. The registry stage-key is
1950
1950
  # reserved exactly once here (inside provision_stage_worktree), and
1951
- # the surrounding mutex makes the registry read in stage selection
1952
- # and that reserve atomic. Other task-types skip this selection;
1951
+ # the surrounding mutex makes the registry read in the claim
1952
+ # and that reserve atomic. Other task-types skip this claim;
1953
1953
  # single-stage final-verification threads its explicit stage via
1954
1954
  # fv_stage_arg, everything else keeps stage_arg=None (flat paths).
1955
1955
  if inp.task_type == "implementation":
1956
- impl_stage_selection = _select_and_provision_implementation_stage(
1956
+ stage_run_claim = _claim_implementation_stage_run(
1957
1957
  inp, ctx_stage_map, task_group_segment, task_id_segment,
1958
1958
  task_key, worktree.status,
1959
1959
  )
1960
- stage_arg = impl_stage_selection.stage
1960
+ stage_arg = stage_run_claim.stage
1961
1961
  # Drop any stale waiver on this stage so the run actually verifies
1962
1962
  # conformance (kept inside the per-task-key mutex so concurrent
1963
1963
  # same-task runs don't race the manifest write).
1964
- _clear_stale_stage_waiver(inp, project_root, impl_stage_selection.stage)
1964
+ _clear_stale_stage_waiver(inp, project_root, stage_run_claim.stage)
1965
1965
  else:
1966
- impl_stage_selection = None
1966
+ stage_run_claim = None
1967
1967
  stage_arg = fv_stage_arg
1968
1968
 
1969
1969
  ctx = compute_and_write_run_context(
@@ -1988,13 +1988,13 @@ def prepare_task_bundle(inp: PrepareInputs) -> PrepareOutputs:
1988
1988
  ),
1989
1989
  })
1990
1990
 
1991
- # implementation: override the task-worktree fields with the resolved
1992
- # STAGE worktree and append the consumers.jsonl started row. Must run
1991
+ # implementation: override the task-worktree fields with the claimed
1992
+ # STAGE worktree. Must run
1993
1993
  # AFTER the task-worktree fields above (mirrors the original ordering
1994
1994
  # where stage reservation overrode them post task-provision).
1995
1995
  if inp.task_type == "implementation":
1996
- _apply_implementation_stage(
1997
- inp, ctx, ctx_stage_map, impl_stage_selection,
1996
+ _publish_stage_run_claim(
1997
+ inp, ctx, ctx_stage_map, stage_run_claim,
1998
1998
  )
1999
1999
 
2000
2000
  if inp.render_only:
@@ -2166,10 +2166,10 @@ def prepare_task_bundle(inp: PrepareInputs) -> PrepareOutputs:
2166
2166
  _provision_settings_symlink(inp)
2167
2167
 
2168
2168
  concurrent_run: dict = {}
2169
- if impl_stage_selection is not None and impl_stage_selection.concurrent_stages:
2169
+ if stage_run_claim is not None and stage_run_claim.concurrent_stages:
2170
2170
  concurrent_run = {
2171
2171
  "detected": True,
2172
- "active_stages": impl_stage_selection.concurrent_stages,
2172
+ "active_stages": stage_run_claim.concurrent_stages,
2173
2173
  }
2174
2174
  return PrepareOutputs(
2175
2175
  ctx=ctx,
@@ -306,6 +306,32 @@ def order_stage_closure(
306
306
  return out
307
307
 
308
308
 
309
+ def downstream_stage_closure(
310
+ stages: list[tuple[int, list[int]]],
311
+ seed: set[int],
312
+ ) -> set[int]:
313
+ """Stages that (transitively) depend on any stage in ``seed``, plus ``seed``.
314
+
315
+ ``stages`` mirrors ``order_stage_closure``: ``(stage_number, [depends_on_numbers])``.
316
+ ``order_stage_closure`` walks dependencies (upstream); this walks dependents
317
+ (downstream) — the reverse edge — so an answered clarification that changes a
318
+ seed stage re-verifies everything built on top of it.
319
+ """
320
+ dependents: dict[int, list[int]] = {}
321
+ for num, deps in stages:
322
+ for dep in deps:
323
+ dependents.setdefault(dep, []).append(num)
324
+ result = set(seed)
325
+ queue = list(seed)
326
+ while queue:
327
+ cur = queue.pop()
328
+ for child in dependents.get(cur, []):
329
+ if child not in result:
330
+ result.add(child)
331
+ queue.append(child)
332
+ return result
333
+
334
+
309
335
  def commit_is_ancestor(project_root: Path, ancestor: str, descendant: str) -> bool:
310
336
  """True iff ``ancestor`` is an ancestor of ``descendant`` in git history."""
311
337
  from .worktree import is_ancestor
@@ -881,7 +881,7 @@ def provision_stage_worktree(
881
881
  before re-raising so a retry is not blocked.
882
882
 
883
883
  Concurrency: callers must hold ``locks.worktree_provision_mutex`` for
884
- the task-key, acquired BEFORE stage selection reads the registry
884
+ the task-key, acquired BEFORE the Stage Run Claim reads the registry
885
885
  (run.py does) — otherwise two `--stage auto` runs can select the same
886
886
  stage and the loser silently enters the winner's worktree via the
887
887
  "reused" path. flock is non-reentrant, hence the lock lives at the
@@ -83,6 +83,28 @@ def _session_meta_from_record(rec: dict) -> tuple[str | None, str | None]:
83
83
  return agent, model
84
84
 
85
85
 
86
+ _SUBAGENT_FILENAME_RE = re.compile(r"^agent-a(?P<agent>.+)-[0-9a-f]{8,}$")
87
+
88
+
89
+ def _agent_name_from_subagent_path(jsonl_path: Path) -> str | None:
90
+ if jsonl_path.parent.name != "subagents":
91
+ return None
92
+ match = _SUBAGENT_FILENAME_RE.match(jsonl_path.stem)
93
+ return match.group("agent") if match else None
94
+
95
+
96
+ def _iter_claude_session_jsonls(
97
+ proj_dir: Path,
98
+ subagent_parent_sids: list[str] | None = None,
99
+ ):
100
+ yield from sorted(proj_dir.glob("*.jsonl"))
101
+ if subagent_parent_sids is None:
102
+ yield from sorted(proj_dir.glob("*/subagents/*.jsonl"))
103
+ return
104
+ for sid in sorted({sid for sid in subagent_parent_sids if sid}):
105
+ yield from sorted((proj_dir / sid / "subagents").glob("*.jsonl"))
106
+
107
+
86
108
  def _advance_usage_scan(jsonl_path: Path, usage_state: dict) -> dict:
87
109
  """`usage_state['offset']` 이후의 완결 라인을 읽어 이벤트를 커밋하고,
88
110
  개행 없는 마지막 라인은 transient 로만 반영한 view 를 돌려준다.
@@ -230,7 +252,8 @@ def claude_session_totals(
230
252
  save_cache(jsonl_path, cache)
231
253
  else:
232
254
  view = _advance_usage_scan(jsonl_path, fresh_cache()["usage"])
233
- return _totals_from_events(view["events"], view["agentName"],
255
+ agent_name = view["agentName"] or _agent_name_from_subagent_path(jsonl_path)
256
+ return _totals_from_events(view["events"], agent_name,
234
257
  view["model"], since, until)
235
258
 
236
259
 
@@ -312,7 +335,7 @@ def find_claude_team_sessions(
312
335
  if not proj_dir.is_dir():
313
336
  return out
314
337
  needles_lower = [f'"teamname":"{n.lower()}"' for n in team_needles if n]
315
- for p in proj_dir.glob("*.jsonl"):
338
+ for p in _iter_claude_session_jsonls(proj_dir):
316
339
  for needle_lower in needles_lower:
317
340
  if incremental:
318
341
  cache = load_cache(p)
@@ -333,26 +356,33 @@ def find_claude_team_sessions(
333
356
  _TEAM_TAG_NEEDLE = '"teamname":"'
334
357
 
335
358
 
359
+ def _agent_name_matches(agent_name: str | None, prefixes: list[str]) -> bool:
360
+ if not agent_name:
361
+ return False
362
+ for prefix in prefixes:
363
+ if agent_name == prefix or agent_name.startswith(f"{prefix}-"):
364
+ return True
365
+ return False
366
+
367
+
336
368
  def find_claude_agent_sessions(
337
369
  cwd: Path,
338
370
  agent_prefixes: list[str],
339
371
  projects_root: Path | None = None,
340
372
  *,
341
373
  incremental: bool = False,
374
+ subagent_parent_sids: list[str] | None = None,
342
375
  ) -> dict[str, Path]:
343
- """Map sessionId -> jsonl path for non-team sessions whose recorded
344
- `agentName` matches one of ``agent_prefixes``.
345
-
346
- no-team run(teamCreate `skipped`(concurrent-run) / `error` fallback)의
347
- worker 세션은 team 태그가 없어 teamName needle 로는 발견되지 않는다. 대신
348
- 하네스가 dispatch `name` 인자로 기록하는 `agentName` needle 로 찾되,
349
- team 태그가 있는 세션은 제외한다 — 그것은 동시 team-mode run 의 worker 다.
350
-
351
- 같은 agentName 쓰는 다른 run 세션도 걸릴 수 있으므로, 호출자는 반드시
352
- run 윈도우로 totals 스코핑하고 in-window 이벤트가 없는 세션을 버려야
353
- 한다. 윈도우가 겹치는 두 no-team run 이 같은 role 을 dispatch 한 경우의
354
- 교차 귀속은 구조적으로 분리 불가 — usage 블록의 sessionId 목록으로만
355
- 추적 가능하다.
376
+ """Map worker-like Claude jsonls by agent name or nested subagent filename.
377
+
378
+ Legacy top-level worker jsonls record `agentName` in the JSONL body. Newer
379
+ in-process subagents can instead live under
380
+ `<lead-session>/subagents/agent-a<name>-<hash>.jsonl` with no body
381
+ `agentName`, so this finder also matches that filename form.
382
+
383
+ Callers can pass `subagent_parent_sids` to limit nested subagents to the
384
+ current lead session directory. Top-level fallback discovery remains broad
385
+ and must still be filtered by the run window after totals are read.
356
386
  """
357
387
  proj_dir = claude_project_dir(cwd, projects_root)
358
388
  out: dict[str, Path] = {}
@@ -361,20 +391,29 @@ def find_claude_agent_sessions(
361
391
  agent_needles = [f'"agentname":"{p.lower()}' for p in agent_prefixes if p]
362
392
  if not agent_needles:
363
393
  return out
364
- for p in proj_dir.glob("*.jsonl"):
394
+ for p in _iter_claude_session_jsonls(proj_dir, subagent_parent_sids):
395
+ path_agent = _agent_name_from_subagent_path(p)
396
+ filename_matched = _agent_name_matches(path_agent, agent_prefixes)
397
+ scoped_subagent = (
398
+ filename_matched
399
+ and path_agent is not None
400
+ and subagent_parent_sids is not None
401
+ )
365
402
  if incremental:
366
403
  cache = load_cache(p)
367
- matched = any(_cached_needle_scan(p, cache, n) for n in agent_needles)
404
+ matched = filename_matched or any(
405
+ _cached_needle_scan(p, cache, n) for n in agent_needles
406
+ )
368
407
  team_tagged = matched and _cached_needle_scan(p, cache, _TEAM_TAG_NEEDLE)
369
408
  save_cache(p, cache)
370
409
  else:
371
- matched = any(
410
+ matched = filename_matched or any(
372
411
  _needle_scan(p, {"offset": 0, "found": False}, n)
373
412
  for n in agent_needles
374
413
  )
375
414
  team_tagged = matched and _needle_scan(
376
415
  p, {"offset": 0, "found": False}, _TEAM_TAG_NEEDLE
377
416
  )
378
- if matched and not team_tagged:
417
+ if matched and (scoped_subagent or not team_tagged):
379
418
  out[p.stem] = p
380
419
  return out
@@ -653,8 +653,10 @@ def collect(team_state_path: Path, project_root: Path | None = None, *,
653
653
  for w in state.get("workers", [])
654
654
  for prefix in match_prefixes(w.get("workerId") or "")
655
655
  ]
656
+ subagent_parent_sids = [lead_sid] if lead_sid else []
656
657
  agent_sessions = find_claude_agent_sessions(
657
- cwd, worker_prefix_pool, incremental=incremental
658
+ cwd, worker_prefix_pool, incremental=incremental,
659
+ subagent_parent_sids=subagent_parent_sids,
658
660
  )
659
661
  for sid, path in agent_sessions.items():
660
662
  if sid == lead_sid or sid in claude_sessions:
@@ -12,6 +12,9 @@ Currently vendored:
12
12
  (`_speedups.c` / `_speedups.pyi`) is stripped; the pure-python
13
13
  `_native` fallback is used at import time.
14
14
  - networkx (BSD-3-Clause) — graph model + community detection for okstra-graphify.
15
+ Per-package `tests/` suites and the top-level `conftest.py` are stripped;
16
+ okstra never runs networkx's own test suite. On re-vendor, drop every
17
+ `**/tests/` directory and `conftest.py` again.
15
18
  - graphify 0.4.23 (MIT) — knowledge-graph pipeline for okstra-graphify. tree-sitter
16
19
  native bindings are NOT vendored; okstra-graphify only indexes .okstra/ docs.
17
20
 
@@ -154,6 +154,26 @@
154
154
  }
155
155
  },
156
156
 
157
+ "readerSummary": {
158
+ "type": "object",
159
+ "description": "Optional reader-first summary rendered near the top of the Markdown report and reused by the HTML reader dashboard. Existing v1.0 data.json files may omit it; new report-writer output should populate it to keep the first-read path short.",
160
+ "required": [
161
+ "decision",
162
+ "humanActionRequired",
163
+ "blockingItems",
164
+ "safeToSkip",
165
+ "recommendedCommand"
166
+ ],
167
+ "additionalProperties": false,
168
+ "properties": {
169
+ "decision": { "type": "string", "minLength": 1 },
170
+ "humanActionRequired": { "type": "string", "minLength": 1 },
171
+ "blockingItems": { "type": "string", "minLength": 1 },
172
+ "safeToSkip": { "type": "string", "minLength": 1 },
173
+ "recommendedCommand": { "type": "string", "minLength": 1 }
174
+ }
175
+ },
176
+
157
177
  "rationale": {
158
178
  "type": "object",
159
179
  "description": "## 작업 배경과 근거 — reviewer-facing narrative answering the four questions in order. Each field is prose (not a table) and MUST carry at least one evidence reference (path:line, a report ID such as C-001/F-013/§5.4, or another cited source) OR an explicit insufficiency marker (e.g. '근거 불충분'). validate-run.py fails a field with neither. Required for every task-type.",
@@ -425,6 +445,24 @@
425
445
  "skippedAdrCandidates": {
426
446
  "type": "array",
427
447
  "items": { "$ref": "#/$defs/SkippedAdrCandidate" }
448
+ },
449
+ "incrementalDecision": {
450
+ "type": "object",
451
+ "description": "Verbatim `okstra incremental-scope` output for a clarification re-run. Present only when this implementation-planning run scoped itself against a prior run; drives the Section 0 incremental audit block.",
452
+ "required": ["mode", "reverifyStages", "carryStages", "reason"],
453
+ "additionalProperties": false,
454
+ "properties": {
455
+ "mode": { "type": "string", "enum": ["incremental", "full"] },
456
+ "reverifyStages": {
457
+ "type": "array",
458
+ "items": { "type": "integer" }
459
+ },
460
+ "carryStages": {
461
+ "type": "array",
462
+ "items": { "type": "integer" }
463
+ },
464
+ "reason": { "type": "string", "minLength": 1 }
465
+ }
428
466
  }
429
467
  }
430
468
  },
@@ -1480,6 +1518,7 @@
1480
1518
  "id": { "type": "string", "minLength": 1 },
1481
1519
  "subject": { "type": "string", "minLength": 1 },
1482
1520
  "sourceSection": { "type": "string" },
1521
+ "carriedForwardFromSeq": { "type": "string" },
1483
1522
  "clarificationId": {
1484
1523
  "anyOf": [
1485
1524
  { "type": "null" },