okstra 0.114.0 → 0.115.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 (326) 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/lead/okstra-lead-contract.md +1 -1
  7. package/runtime/python/okstra_ctl/implementation_stage.py +46 -39
  8. package/runtime/python/okstra_ctl/report_views.py +151 -12
  9. package/runtime/python/okstra_ctl/run.py +24 -24
  10. package/runtime/python/okstra_ctl/worktree.py +1 -1
  11. package/runtime/python/okstra_token_usage/claude.py +58 -19
  12. package/runtime/python/okstra_token_usage/collect.py +3 -1
  13. package/runtime/python/okstra_vendor/__init__.py +3 -0
  14. package/runtime/schemas/final-report-v1.0.schema.json +20 -0
  15. package/runtime/templates/reports/final-report.template.md +14 -0
  16. package/runtime/templates/reports/i18n/en.json +23 -12
  17. package/runtime/templates/reports/i18n/ko.json +23 -12
  18. package/runtime/templates/reports/report.css +55 -0
  19. package/runtime/templates/reports/report.js +53 -1
  20. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/__init__.py +0 -0
  21. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_approx_clust_coeff.py +0 -41
  22. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_clique.py +0 -112
  23. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_connectivity.py +0 -199
  24. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_density.py +0 -146
  25. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_distance_measures.py +0 -59
  26. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_dominating_set.py +0 -78
  27. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_kcomponents.py +0 -303
  28. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_matching.py +0 -8
  29. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_maxcut.py +0 -94
  30. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_ramsey.py +0 -31
  31. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_steinertree.py +0 -306
  32. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_traveling_salesman.py +0 -1014
  33. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_treewidth.py +0 -274
  34. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_vertex_cover.py +0 -68
  35. package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/tests/__init__.py +0 -0
  36. package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/tests/base_test.py +0 -81
  37. package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/tests/test_connectivity.py +0 -143
  38. package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/tests/test_correlation.py +0 -122
  39. package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/tests/test_mixing.py +0 -174
  40. package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/tests/test_neighbor_degree.py +0 -107
  41. package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/tests/test_pairs.py +0 -87
  42. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/__init__.py +0 -0
  43. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_basic.py +0 -125
  44. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_centrality.py +0 -192
  45. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_cluster.py +0 -84
  46. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_covering.py +0 -33
  47. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_edgelist.py +0 -240
  48. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_extendability.py +0 -334
  49. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_generators.py +0 -407
  50. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_link_analysis.py +0 -218
  51. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_matching.py +0 -327
  52. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_matrix.py +0 -138
  53. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_project.py +0 -409
  54. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_redundancy.py +0 -35
  55. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_spectral_bipartivity.py +0 -80
  56. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/__init__.py +0 -0
  57. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_betweenness_centrality.py +0 -923
  58. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_betweenness_centrality_subset.py +0 -354
  59. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_closeness_centrality.py +0 -274
  60. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_current_flow_betweenness_centrality.py +0 -259
  61. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_current_flow_betweenness_centrality_subset.py +0 -147
  62. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_current_flow_closeness.py +0 -43
  63. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_degree_centrality.py +0 -144
  64. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_dispersion.py +0 -73
  65. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_eigenvector_centrality.py +0 -186
  66. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_group.py +0 -277
  67. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_harmonic_centrality.py +0 -122
  68. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_katz_centrality.py +0 -345
  69. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_laplacian_centrality.py +0 -220
  70. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_load_centrality.py +0 -344
  71. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_percolation_centrality.py +0 -87
  72. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_reaching.py +0 -140
  73. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_second_order_centrality.py +0 -82
  74. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_subgraph.py +0 -110
  75. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_trophic.py +0 -302
  76. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_voterank.py +0 -64
  77. package/runtime/python/okstra_vendor/networkx/algorithms/coloring/tests/__init__.py +0 -0
  78. package/runtime/python/okstra_vendor/networkx/algorithms/coloring/tests/test_coloring.py +0 -863
  79. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/__init__.py +0 -0
  80. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_asyn_fluid.py +0 -147
  81. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_bipartitions.py +0 -157
  82. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_centrality.py +0 -85
  83. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_divisive.py +0 -106
  84. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_kclique.py +0 -91
  85. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_label_propagation.py +0 -241
  86. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_leiden.py +0 -138
  87. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_local.py +0 -76
  88. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_louvain.py +0 -264
  89. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_lukes.py +0 -152
  90. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_modularity_max.py +0 -340
  91. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_quality.py +0 -139
  92. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_utils.py +0 -26
  93. package/runtime/python/okstra_vendor/networkx/algorithms/components/tests/__init__.py +0 -0
  94. package/runtime/python/okstra_vendor/networkx/algorithms/components/tests/test_attracting.py +0 -70
  95. package/runtime/python/okstra_vendor/networkx/algorithms/components/tests/test_biconnected.py +0 -248
  96. package/runtime/python/okstra_vendor/networkx/algorithms/components/tests/test_connected.py +0 -138
  97. package/runtime/python/okstra_vendor/networkx/algorithms/components/tests/test_semiconnected.py +0 -55
  98. package/runtime/python/okstra_vendor/networkx/algorithms/components/tests/test_strongly_connected.py +0 -193
  99. package/runtime/python/okstra_vendor/networkx/algorithms/components/tests/test_weakly_connected.py +0 -96
  100. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/tests/__init__.py +0 -0
  101. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/tests/test_connectivity.py +0 -421
  102. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/tests/test_cuts.py +0 -309
  103. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/tests/test_disjoint_paths.py +0 -249
  104. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/tests/test_edge_augmentation.py +0 -502
  105. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/tests/test_edge_kcomponents.py +0 -488
  106. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/tests/test_kcomponents.py +0 -323
  107. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/tests/test_kcutsets.py +0 -280
  108. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/tests/test_stoer_wagner.py +0 -102
  109. package/runtime/python/okstra_vendor/networkx/algorithms/flow/tests/__init__.py +0 -0
  110. package/runtime/python/okstra_vendor/networkx/algorithms/flow/tests/gl1.gpickle.bz2 +0 -0
  111. package/runtime/python/okstra_vendor/networkx/algorithms/flow/tests/gw1.gpickle.bz2 +0 -0
  112. package/runtime/python/okstra_vendor/networkx/algorithms/flow/tests/netgen-2.gpickle.bz2 +0 -0
  113. package/runtime/python/okstra_vendor/networkx/algorithms/flow/tests/test_gomory_hu.py +0 -128
  114. package/runtime/python/okstra_vendor/networkx/algorithms/flow/tests/test_maxflow.py +0 -573
  115. package/runtime/python/okstra_vendor/networkx/algorithms/flow/tests/test_maxflow_large_graph.py +0 -155
  116. package/runtime/python/okstra_vendor/networkx/algorithms/flow/tests/test_mincost.py +0 -475
  117. package/runtime/python/okstra_vendor/networkx/algorithms/flow/tests/test_networksimplex.py +0 -481
  118. package/runtime/python/okstra_vendor/networkx/algorithms/flow/tests/wlm3.gpickle.bz2 +0 -0
  119. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/__init__.py +0 -0
  120. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/iso_r01_s80.A99 +0 -0
  121. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/iso_r01_s80.B99 +0 -0
  122. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/si2_b06_m200.A99 +0 -0
  123. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/si2_b06_m200.B99 +0 -0
  124. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/test_ismags.py +0 -719
  125. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/test_isomorphism.py +0 -103
  126. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/test_isomorphvf2.py +0 -490
  127. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/test_match_helpers.py +0 -64
  128. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/test_temporalisomorphvf2.py +0 -212
  129. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/test_tree_isomorphism.py +0 -202
  130. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/test_vf2pp.py +0 -1655
  131. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/test_vf2pp_helpers.py +0 -3118
  132. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/test_vf2userfunc.py +0 -196
  133. package/runtime/python/okstra_vendor/networkx/algorithms/link_analysis/tests/__init__.py +0 -0
  134. package/runtime/python/okstra_vendor/networkx/algorithms/link_analysis/tests/test_hits.py +0 -77
  135. package/runtime/python/okstra_vendor/networkx/algorithms/link_analysis/tests/test_pagerank.py +0 -213
  136. package/runtime/python/okstra_vendor/networkx/algorithms/minors/tests/test_contraction.py +0 -544
  137. package/runtime/python/okstra_vendor/networkx/algorithms/operators/tests/__init__.py +0 -0
  138. package/runtime/python/okstra_vendor/networkx/algorithms/operators/tests/test_all.py +0 -328
  139. package/runtime/python/okstra_vendor/networkx/algorithms/operators/tests/test_binary.py +0 -451
  140. package/runtime/python/okstra_vendor/networkx/algorithms/operators/tests/test_product.py +0 -491
  141. package/runtime/python/okstra_vendor/networkx/algorithms/operators/tests/test_unary.py +0 -55
  142. package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/tests/__init__.py +0 -0
  143. package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/tests/test_astar.py +0 -254
  144. package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/tests/test_dense.py +0 -212
  145. package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/tests/test_dense_numpy.py +0 -88
  146. package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/tests/test_generic.py +0 -511
  147. package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/tests/test_unweighted.py +0 -149
  148. package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/tests/test_weighted.py +0 -983
  149. package/runtime/python/okstra_vendor/networkx/algorithms/tests/__init__.py +0 -0
  150. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_asteroidal.py +0 -23
  151. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_boundary.py +0 -154
  152. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_bridges.py +0 -144
  153. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_broadcasting.py +0 -109
  154. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_chains.py +0 -136
  155. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_chordal.py +0 -129
  156. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_clique.py +0 -300
  157. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_cluster.py +0 -678
  158. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_communicability.py +0 -80
  159. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_core.py +0 -266
  160. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_covering.py +0 -85
  161. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_cuts.py +0 -171
  162. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_cycles.py +0 -984
  163. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_d_separation.py +0 -340
  164. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_dag.py +0 -835
  165. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_distance_measures.py +0 -831
  166. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_distance_regular.py +0 -85
  167. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_dominance.py +0 -299
  168. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_dominating.py +0 -115
  169. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_efficiency.py +0 -58
  170. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_euler.py +0 -314
  171. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_graph_hashing.py +0 -872
  172. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_graphical.py +0 -163
  173. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_hierarchy.py +0 -46
  174. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_hybrid.py +0 -24
  175. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_isolate.py +0 -26
  176. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_link_prediction.py +0 -615
  177. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_lowest_common_ancestors.py +0 -459
  178. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_matching.py +0 -556
  179. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_max_weight_clique.py +0 -179
  180. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_mis.py +0 -62
  181. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_moral.py +0 -15
  182. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_node_classification.py +0 -140
  183. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_non_randomness.py +0 -60
  184. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_perfect_graph.py +0 -27
  185. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_planar_drawing.py +0 -274
  186. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_planarity.py +0 -556
  187. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_polynomials.py +0 -57
  188. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_reciprocity.py +0 -37
  189. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_regular.py +0 -88
  190. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_richclub.py +0 -149
  191. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_similarity.py +0 -1158
  192. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_simple_paths.py +0 -803
  193. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_smallworld.py +0 -76
  194. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_smetric.py +0 -8
  195. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_sparsifiers.py +0 -138
  196. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_structuralholes.py +0 -191
  197. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_summarization.py +0 -642
  198. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_swap.py +0 -179
  199. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_threshold.py +0 -270
  200. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_time_dependent.py +0 -431
  201. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_tournament.py +0 -161
  202. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_triads.py +0 -248
  203. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_vitality.py +0 -41
  204. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_voronoi.py +0 -103
  205. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_walks.py +0 -54
  206. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_wiener.py +0 -157
  207. package/runtime/python/okstra_vendor/networkx/algorithms/traversal/tests/__init__.py +0 -0
  208. package/runtime/python/okstra_vendor/networkx/algorithms/traversal/tests/test_beamsearch.py +0 -25
  209. package/runtime/python/okstra_vendor/networkx/algorithms/traversal/tests/test_bfs.py +0 -203
  210. package/runtime/python/okstra_vendor/networkx/algorithms/traversal/tests/test_dfs.py +0 -307
  211. package/runtime/python/okstra_vendor/networkx/algorithms/traversal/tests/test_edgebfs.py +0 -147
  212. package/runtime/python/okstra_vendor/networkx/algorithms/traversal/tests/test_edgedfs.py +0 -131
  213. package/runtime/python/okstra_vendor/networkx/algorithms/tree/tests/__init__.py +0 -0
  214. package/runtime/python/okstra_vendor/networkx/algorithms/tree/tests/test_branchings.py +0 -624
  215. package/runtime/python/okstra_vendor/networkx/algorithms/tree/tests/test_coding.py +0 -114
  216. package/runtime/python/okstra_vendor/networkx/algorithms/tree/tests/test_decomposition.py +0 -79
  217. package/runtime/python/okstra_vendor/networkx/algorithms/tree/tests/test_distance_measures.py +0 -99
  218. package/runtime/python/okstra_vendor/networkx/algorithms/tree/tests/test_mst.py +0 -934
  219. package/runtime/python/okstra_vendor/networkx/algorithms/tree/tests/test_operations.py +0 -53
  220. package/runtime/python/okstra_vendor/networkx/algorithms/tree/tests/test_recognition.py +0 -174
  221. package/runtime/python/okstra_vendor/networkx/classes/tests/__init__.py +0 -0
  222. package/runtime/python/okstra_vendor/networkx/classes/tests/dispatch_interface.py +0 -192
  223. package/runtime/python/okstra_vendor/networkx/classes/tests/historical_tests.py +0 -476
  224. package/runtime/python/okstra_vendor/networkx/classes/tests/test_coreviews.py +0 -362
  225. package/runtime/python/okstra_vendor/networkx/classes/tests/test_digraph.py +0 -331
  226. package/runtime/python/okstra_vendor/networkx/classes/tests/test_digraph_historical.py +0 -110
  227. package/runtime/python/okstra_vendor/networkx/classes/tests/test_filters.py +0 -177
  228. package/runtime/python/okstra_vendor/networkx/classes/tests/test_function.py +0 -1045
  229. package/runtime/python/okstra_vendor/networkx/classes/tests/test_graph.py +0 -950
  230. package/runtime/python/okstra_vendor/networkx/classes/tests/test_graph_historical.py +0 -12
  231. package/runtime/python/okstra_vendor/networkx/classes/tests/test_graphviews.py +0 -349
  232. package/runtime/python/okstra_vendor/networkx/classes/tests/test_multidigraph.py +0 -459
  233. package/runtime/python/okstra_vendor/networkx/classes/tests/test_multigraph.py +0 -528
  234. package/runtime/python/okstra_vendor/networkx/classes/tests/test_reportviews.py +0 -1421
  235. package/runtime/python/okstra_vendor/networkx/classes/tests/test_special.py +0 -131
  236. package/runtime/python/okstra_vendor/networkx/classes/tests/test_subgraphviews.py +0 -371
  237. package/runtime/python/okstra_vendor/networkx/conftest.py +0 -261
  238. package/runtime/python/okstra_vendor/networkx/drawing/tests/__init__.py +0 -0
  239. package/runtime/python/okstra_vendor/networkx/drawing/tests/baseline/test_display_complex.png +0 -0
  240. package/runtime/python/okstra_vendor/networkx/drawing/tests/baseline/test_display_empty_graph.png +0 -0
  241. package/runtime/python/okstra_vendor/networkx/drawing/tests/baseline/test_display_house_with_colors.png +0 -0
  242. package/runtime/python/okstra_vendor/networkx/drawing/tests/baseline/test_display_labels_and_colors.png +0 -0
  243. package/runtime/python/okstra_vendor/networkx/drawing/tests/baseline/test_display_shortest_path.png +0 -0
  244. package/runtime/python/okstra_vendor/networkx/drawing/tests/baseline/test_house_with_colors.png +0 -0
  245. package/runtime/python/okstra_vendor/networkx/drawing/tests/test_agraph.py +0 -237
  246. package/runtime/python/okstra_vendor/networkx/drawing/tests/test_image_comparison_pylab_mpl.py +0 -229
  247. package/runtime/python/okstra_vendor/networkx/drawing/tests/test_latex.py +0 -285
  248. package/runtime/python/okstra_vendor/networkx/drawing/tests/test_layout.py +0 -631
  249. package/runtime/python/okstra_vendor/networkx/drawing/tests/test_pydot.py +0 -146
  250. package/runtime/python/okstra_vendor/networkx/drawing/tests/test_pylab.py +0 -1582
  251. package/runtime/python/okstra_vendor/networkx/generators/tests/__init__.py +0 -0
  252. package/runtime/python/okstra_vendor/networkx/generators/tests/test_atlas.py +0 -75
  253. package/runtime/python/okstra_vendor/networkx/generators/tests/test_classic.py +0 -642
  254. package/runtime/python/okstra_vendor/networkx/generators/tests/test_cographs.py +0 -20
  255. package/runtime/python/okstra_vendor/networkx/generators/tests/test_community.py +0 -362
  256. package/runtime/python/okstra_vendor/networkx/generators/tests/test_degree_seq.py +0 -224
  257. package/runtime/python/okstra_vendor/networkx/generators/tests/test_directed.py +0 -189
  258. package/runtime/python/okstra_vendor/networkx/generators/tests/test_duplication.py +0 -103
  259. package/runtime/python/okstra_vendor/networkx/generators/tests/test_ego.py +0 -39
  260. package/runtime/python/okstra_vendor/networkx/generators/tests/test_expanders.py +0 -182
  261. package/runtime/python/okstra_vendor/networkx/generators/tests/test_geometric.py +0 -488
  262. package/runtime/python/okstra_vendor/networkx/generators/tests/test_harary_graph.py +0 -133
  263. package/runtime/python/okstra_vendor/networkx/generators/tests/test_internet_as_graphs.py +0 -221
  264. package/runtime/python/okstra_vendor/networkx/generators/tests/test_intersection.py +0 -28
  265. package/runtime/python/okstra_vendor/networkx/generators/tests/test_interval_graph.py +0 -144
  266. package/runtime/python/okstra_vendor/networkx/generators/tests/test_joint_degree_seq.py +0 -125
  267. package/runtime/python/okstra_vendor/networkx/generators/tests/test_lattice.py +0 -264
  268. package/runtime/python/okstra_vendor/networkx/generators/tests/test_line.py +0 -316
  269. package/runtime/python/okstra_vendor/networkx/generators/tests/test_mycielski.py +0 -30
  270. package/runtime/python/okstra_vendor/networkx/generators/tests/test_nonisomorphic_trees.py +0 -82
  271. package/runtime/python/okstra_vendor/networkx/generators/tests/test_random_clustered.py +0 -33
  272. package/runtime/python/okstra_vendor/networkx/generators/tests/test_random_graphs.py +0 -495
  273. package/runtime/python/okstra_vendor/networkx/generators/tests/test_small.py +0 -220
  274. package/runtime/python/okstra_vendor/networkx/generators/tests/test_spectral_graph_forge.py +0 -49
  275. package/runtime/python/okstra_vendor/networkx/generators/tests/test_stochastic.py +0 -72
  276. package/runtime/python/okstra_vendor/networkx/generators/tests/test_sudoku.py +0 -92
  277. package/runtime/python/okstra_vendor/networkx/generators/tests/test_time_series.py +0 -64
  278. package/runtime/python/okstra_vendor/networkx/generators/tests/test_trees.py +0 -195
  279. package/runtime/python/okstra_vendor/networkx/generators/tests/test_triads.py +0 -15
  280. package/runtime/python/okstra_vendor/networkx/linalg/tests/__init__.py +0 -0
  281. package/runtime/python/okstra_vendor/networkx/linalg/tests/test_algebraic_connectivity.py +0 -400
  282. package/runtime/python/okstra_vendor/networkx/linalg/tests/test_attrmatrix.py +0 -108
  283. package/runtime/python/okstra_vendor/networkx/linalg/tests/test_bethehessian.py +0 -40
  284. package/runtime/python/okstra_vendor/networkx/linalg/tests/test_graphmatrix.py +0 -275
  285. package/runtime/python/okstra_vendor/networkx/linalg/tests/test_laplacian.py +0 -334
  286. package/runtime/python/okstra_vendor/networkx/linalg/tests/test_modularity.py +0 -86
  287. package/runtime/python/okstra_vendor/networkx/linalg/tests/test_spectrum.py +0 -70
  288. package/runtime/python/okstra_vendor/networkx/readwrite/json_graph/tests/__init__.py +0 -0
  289. package/runtime/python/okstra_vendor/networkx/readwrite/json_graph/tests/test_adjacency.py +0 -78
  290. package/runtime/python/okstra_vendor/networkx/readwrite/json_graph/tests/test_cytoscape.py +0 -78
  291. package/runtime/python/okstra_vendor/networkx/readwrite/json_graph/tests/test_node_link.py +0 -109
  292. package/runtime/python/okstra_vendor/networkx/readwrite/json_graph/tests/test_tree.py +0 -48
  293. package/runtime/python/okstra_vendor/networkx/readwrite/tests/__init__.py +0 -0
  294. package/runtime/python/okstra_vendor/networkx/readwrite/tests/test_adjlist.py +0 -354
  295. package/runtime/python/okstra_vendor/networkx/readwrite/tests/test_edgelist.py +0 -318
  296. package/runtime/python/okstra_vendor/networkx/readwrite/tests/test_gexf.py +0 -612
  297. package/runtime/python/okstra_vendor/networkx/readwrite/tests/test_gml.py +0 -744
  298. package/runtime/python/okstra_vendor/networkx/readwrite/tests/test_graph6.py +0 -181
  299. package/runtime/python/okstra_vendor/networkx/readwrite/tests/test_graphml.py +0 -1531
  300. package/runtime/python/okstra_vendor/networkx/readwrite/tests/test_leda.py +0 -30
  301. package/runtime/python/okstra_vendor/networkx/readwrite/tests/test_p2g.py +0 -63
  302. package/runtime/python/okstra_vendor/networkx/readwrite/tests/test_pajek.py +0 -128
  303. package/runtime/python/okstra_vendor/networkx/readwrite/tests/test_sparse6.py +0 -166
  304. package/runtime/python/okstra_vendor/networkx/readwrite/tests/test_text.py +0 -1742
  305. package/runtime/python/okstra_vendor/networkx/tests/__init__.py +0 -0
  306. package/runtime/python/okstra_vendor/networkx/tests/test_all_random_functions.py +0 -248
  307. package/runtime/python/okstra_vendor/networkx/tests/test_convert.py +0 -321
  308. package/runtime/python/okstra_vendor/networkx/tests/test_convert_numpy.py +0 -531
  309. package/runtime/python/okstra_vendor/networkx/tests/test_convert_pandas.py +0 -349
  310. package/runtime/python/okstra_vendor/networkx/tests/test_convert_scipy.py +0 -281
  311. package/runtime/python/okstra_vendor/networkx/tests/test_exceptions.py +0 -40
  312. package/runtime/python/okstra_vendor/networkx/tests/test_import.py +0 -11
  313. package/runtime/python/okstra_vendor/networkx/tests/test_lazy_imports.py +0 -96
  314. package/runtime/python/okstra_vendor/networkx/tests/test_relabel.py +0 -349
  315. package/runtime/python/okstra_vendor/networkx/tests/test_removed_functions_exception_messages.py +0 -8
  316. package/runtime/python/okstra_vendor/networkx/utils/tests/__init__.py +0 -0
  317. package/runtime/python/okstra_vendor/networkx/utils/tests/test__init.py +0 -11
  318. package/runtime/python/okstra_vendor/networkx/utils/tests/test_backends.py +0 -225
  319. package/runtime/python/okstra_vendor/networkx/utils/tests/test_config.py +0 -263
  320. package/runtime/python/okstra_vendor/networkx/utils/tests/test_decorators.py +0 -510
  321. package/runtime/python/okstra_vendor/networkx/utils/tests/test_heaps.py +0 -131
  322. package/runtime/python/okstra_vendor/networkx/utils/tests/test_mapped_queue.py +0 -268
  323. package/runtime/python/okstra_vendor/networkx/utils/tests/test_misc.py +0 -393
  324. package/runtime/python/okstra_vendor/networkx/utils/tests/test_random_sequence.py +0 -53
  325. package/runtime/python/okstra_vendor/networkx/utils/tests/test_rcm.py +0 -63
  326. package/runtime/python/okstra_vendor/networkx/utils/tests/test_unionfind.py +0 -55
@@ -235,7 +235,7 @@ per-process 환경 변수에 task 정체성·경로·workflow 상태를 보관
235
235
  - standard workflow의 기본 worker role은 `Claude worker`, `Codex worker`, `Report writer worker`이며, `Antigravity worker`는 `--workers` 또는 프로필에서 명시할 때만 포함되는 옵션입니다.
236
236
  - worker 역할 분담과 최종 판단은 Claude가 task bundle을 읽고 수행합니다.
237
237
  - 사용자 홈에 설치된 okstra Claude assets(`~/.claude/skills`, `~/.claude/agents`) 는 `Agent(name: ...)` 로 워커를 dispatch 하도록 Claude 를 유도합니다 — 워커는 세션의 implicit team 에 자동 합류합니다.
238
- - **팀 lifecycle (Claude Code v2.1.178+)**: v2.1.178 이 `TeamCreate` / `TeamDelete` 도구와 `Agent(...)` 의 `team_name` 파라미터를 제거했습니다. `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1`(`okstra install` 이 `settings.json` 에 시드) 이면 세션마다 implicit team 1개가 시작 시점에 자동 생성됩니다. lead 는 Phase 3 에서 팀을 만드는 도구를 호출하지 않고, `teamName` audit 라벨과 `teamCreate: { attempted: false, status: "implicit", splitPane: <$TMUX 유무> }` 만 기록한 뒤 워커를 `Agent(name: "<role>-worker", run_in_background: true)`(team_name 없음)로 dispatch 합니다. split-pane teammate 는 `$TMUX` 가 설정돼 있고 `teammateMode: auto` 일 때 나타나며, tmux 밖이면 in-process 로 돕니다(둘 다 정상). run 종료 시 Phase 7 토큰 집계 이후 잔여 tmux pane 정리를 확인하고, split-pane run 에서만 worker teammate 를 정리할지 확인합니다. 승인 시 `okstra-team-reconcile.sh` 로 dead-pane stale-active 멤버를 inactive 로 정리하고 각 완료 teammate 에 `SendMessage` shutdown_request 를 보냅니다 — implicit team 자체를 지우는 도구는 없으며, 팀은 세션 종료와 함께 사라집니다. 사용자가 유지하면 teammate 는 FleetView roster 에 남고, lead 는 Teams/FleetView 에서 제거하라고 안내합니다 (`prompts/profiles/_common-contract.md` 의 *Run-end teammate teardown*). Phase 7 토큰 집계는 `teamCreate.status` 가 `implicit`/`skipped`/`error` 일 때 `agentName` 기반으로 워커 세션을 찾습니다(implicit team 은 jsonl`session-<leadSid>` 태깅하므로 okstra `teamName` 라벨 needle 만으로는 lead 만 잡힘).
238
+ - **팀 lifecycle (Claude Code v2.1.178+)**: v2.1.178 이 `TeamCreate` / `TeamDelete` 도구와 `Agent(...)` 의 `team_name` 파라미터를 제거했습니다. `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1`(`okstra install` 이 `settings.json` 에 시드) 이면 세션마다 implicit team 1개가 시작 시점에 자동 생성됩니다. lead 는 Phase 3 에서 팀을 만드는 도구를 호출하지 않고, `teamName` audit 라벨과 `teamCreate: { attempted: false, status: "implicit", splitPane: <$TMUX 유무> }` 만 기록한 뒤 워커를 `Agent(name: "<role>-worker", run_in_background: true)`(team_name 없음)로 dispatch 합니다. split-pane teammate 는 `$TMUX` 가 설정돼 있고 `teammateMode: auto` 일 때 나타나며, tmux 밖이면 in-process 로 돕니다(둘 다 정상). run 종료 시 Phase 7 토큰 집계 이후 잔여 tmux pane 정리를 확인하고, split-pane run 에서만 worker teammate 를 정리할지 확인합니다. 승인 시 `okstra-team-reconcile.sh` 로 dead-pane stale-active 멤버를 inactive 로 정리하고 각 완료 teammate 에 `SendMessage` shutdown_request 를 보냅니다 — implicit team 자체를 지우는 도구는 없으며, 팀은 세션 종료와 함께 사라집니다. 사용자가 유지하면 teammate 는 FleetView roster 에 남고, lead 는 Teams/FleetView 에서 제거하라고 안내합니다 (`prompts/profiles/_common-contract.md` 의 *Run-end teammate teardown*). Phase 7 토큰 집계는 `teamCreate.status` 가 `implicit`/`skipped`/`error` 일 때 top-level `agentName` 또는 nested `subagents/agent-a<name>-<hash>.jsonl` 파일명 기반으로 워커 세션을 찾습니다.
239
239
 
240
240
  ## Claude prompt contract
241
241
 
@@ -698,7 +698,7 @@ Phase 7 step 1.5 가 final-report MD 한 본을 입력으로 self-contained HTML
698
698
 
699
699
  - Helper CLI: `scripts/okstra-token-usage.py`
700
700
  - 수집 소스:
701
- - Claude lead/workers: `~/.claude/projects/<cwd-as-dashes>/<sessionId>.jsonl`의 per-message `message.usage`
701
+ - Claude lead/workers: `~/.claude/projects/<cwd-as-dashes>/<sessionId>.jsonl` 또는 `~/.claude/projects/<cwd-as-dashes>/<lead-session>/subagents/agent-a<worker-name>-<hash>.jsonl` 의 per-message `message.usage`. nested subagent 파일은 파일명에서 worker name 을 복원하고, 현재 run 의 `team-state.lead.sessionId` 디렉터리 아래만 집계합니다.
702
702
  - Codex CLI: `~/.agent/sessions/Y/M/D/rollout-*.jsonl`의 마지막 `total_token_usage.total_tokens`
703
703
  - Antigravity CLI: `~/.antigravity/tmp/*/chats/session-*.json`의 per-message `tokens.total`
704
704
  - billable-equivalent token math와 USD cost estimation을 함께 기록합니다. Anthropic billing ratio(`cache_creation_5m=1.25x`, `cache_creation_1h=2.0x`, `cache_read=0.1x`, `output=5x`)를 반영합니다. transcript 의 `usage.cache_creation.ephemeral_5m_input_tokens` / `ephemeral_1h_input_tokens` 분해가 있으면 분리 집계합니다.
@@ -0,0 +1,51 @@
1
+ # Final Report 가독성 후속: 3안 재검토
2
+
3
+ 상태: 보류
4
+ 작성일: 2026-07-10
5
+
6
+ ## 배경
7
+
8
+ 이번 1차 가독성 개선은 범위를 좁게 유지했다.
9
+
10
+ - `readerSummary`로 Markdown final-report 앞부분에 사람이 먼저 읽을 요약 진입점을 둔다.
11
+ - HTML report view에 Reader Summary dashboard와 reader mode를 추가한다.
12
+ - 기존 `final-report` data는 `verdictCard` fallback으로 계속 호환한다.
13
+
14
+ 이 문서는 더 큰 3안을 현재 변경에 섞지 않고, 나중에 별도 판단할 재검토 후보로 남긴다.
15
+
16
+ ## 보류한 3안
17
+
18
+ `final-report` 산출물 자체를 목적별로 분리할지 재검토한다.
19
+
20
+ 현재 구조는 하나의 canonical Markdown 문서와 그 문서에서 생성되는 view를 유지한다. 3안은 요약 블록이나 HTML 필터링만으로 충분한지, 아니면 report를 목적별 artifact로 나누는 편이 더 나은지 비교하는 작업이다.
21
+
22
+ 비교 후보:
23
+
24
+ - 짧은 human handoff report와 별도 audit appendix.
25
+ - 같은 `data.json`에서 생성되는 `summary` view와 `audit` view.
26
+ - 사람이 먼저 행동하는 섹션만 남기는 phase별 slim report template.
27
+ - reporter handoff, implementer handoff, verifier audit, release handoff 같은 role별 view set.
28
+
29
+ ## 재검토 트리거
30
+
31
+ 아래 중 하나가 반복되면 이 문서를 다시 연다.
32
+
33
+ - `readerSummary`와 HTML reader mode 배포 후에도 사용자가 `final-report`를 읽기 어렵다고 말한다.
34
+ - 리뷰어가 항상 같은 audit 섹션을 건너뛰거나 더 작은 handoff artifact를 요구한다.
35
+ - `final-report` 생성에서 같은 evidence가 여러 섹션에 반복된다.
36
+ - 향후 schema migration에서 report artifact 계약을 어차피 건드려야 한다.
37
+
38
+ ## 결정할 질문
39
+
40
+ - Markdown이 계속 primary human artifact인가, 아니면 `data.json`만 canonical source로 두고 view를 생성할 것인가?
41
+ - follow-up task에 기본 첨부할 artifact는 무엇인가?
42
+ - 어떤 섹션이 human-action material이고, 어떤 섹션이 audit-only material인가?
43
+ - 분리를 validator가 강제해야 하는가, renderer convention으로 둘 것인가?
44
+ - 분리가 기존 `render-views`, follow-up spawning, approval sidecar에 어떤 영향을 주는가?
45
+
46
+ ## 참조
47
+
48
+ - 현재 schema: `schemas/final-report-v1.0.schema.json`
49
+ - 현재 Markdown template: `templates/reports/final-report.template.md`
50
+ - 현재 HTML renderer: `scripts/okstra_ctl/report_views.py`
51
+ - 현재 report UI assets: `templates/reports/report.css`, `templates/reports/report.js`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "okstra",
3
- "version": "0.114.0",
3
+ "version": "0.115.0",
4
4
  "description": "Multi-agent cross-verification orchestrator runtime + Claude Code skills.",
5
5
  "license": "MIT",
6
6
  "author": "devonshin",
@@ -1,5 +1,5 @@
1
1
  {
2
- "package": "0.114.0",
3
- "builtAt": "2026-07-08T17:31:16.739Z",
2
+ "package": "0.115.0",
3
+ "builtAt": "2026-07-09T15:25:58.950Z",
4
4
  "repoRoot": "/home/runner/work/okstra/okstra"
5
5
  }
@@ -88,6 +88,7 @@ Rules (the schema enforces most of these — they are listed here so you know *w
88
88
  - `header.reportAuthor` is `"Report writer worker"`; `header.reportOwner` is `"Claude lead"`. Set author to `"Claude lead"` only for `release-handoff` runs (single-lead by design) or a recorded report-writer dispatch failure fallback.
89
89
  - **Source items (worker:item) preservation.** Every `consensus[].sourceItems`, `differences[].workersPosition[].itemId`, and `evidence.primary[].sourceItems` entry MUST carry the worker:item-id pair (e.g. `claude:F-001`, `codex:1.1`, `antigravity:F-3`, or `lead:mcp-1` for lead-only evidence). The schema enforces this via the `SourceItem` regex; bare worker-name lists no longer parse.
90
90
  - **Verdict Card consistency.** `verdictCard.verdictToken` / `.direction` / `.nextStep` MUST byte-match `finalVerdict.verdictToken` / `.direction` / `.nextStep` and `recommendedNextSteps[0].text`. The renderer pulls both from the same data structure — duplicating values across `verdictCard` and `finalVerdict` is intentional so the validator can diff them.
91
+ - **Reader Summary.** Populate `readerSummary` when the schema excerpt exposes it. It is the human-first entrypoint for both Markdown and HTML: one sentence for the decision, one for the human action required, one for blockers, one for audit sections safe to skip on first read, and one runnable recommended command. Do not duplicate raw evidence tables here.
91
92
  - **§7 phase-continuation row (mandatory for non-terminal task-types).** When `header.taskType` is one of `requirements-discovery` / `implementation-planning` / `error-analysis` / `implementation` / `final-verification`, `followUpTasks` MUST contain at least one row whose `origin` is `phase-continuation`, `suggestedTaskType` equals the next phase (byte-identical to `finalVerdict.nextStep`'s referenced phase), `newTaskId` reuses the current task-id, `autoSpawn` is `"no"`, and `priority` is `"P0"`. For `release-handoff` runs, omit the phase-continuation row. Schema `allOf` clause enforces this via `contains`.
92
93
  - **No deprecated sections.** The schema has no `4.5.8 User Approval Request` body field, no `4.5.9 Open Questions`, no `5.1 추가 자료 요청`, no `5.2 사용자 확인 질문` — clarifications go under the unified `clarificationItems[]` array.
93
94
  - **Optional Section 0.** Include `clarificationCarryIn` ONLY when the lead's prompt provides a non-empty carry-in path. Omit the key entirely otherwise (do NOT set it to `null` or an empty object).
@@ -366,7 +366,7 @@ jq -s 'group_by(.errorType) | map({type: .[0].errorType, count: length})' <runDi
366
366
 
367
367
  The errors log is informational. Its presence/absence does not affect the final verdict. Do not block report writing on it.
368
368
 
369
- After persistence, reply briefly in the resolved Report Language with: completion status, final report path, team-state path, validator result, resume command path, any remaining blocker.
369
+ After persistence, reply briefly in the resolved Report Language with: completion status, final report path, team-state path, validator result, resume command path, any remaining blocker. **Lead this reply with the run's task identity** — state `<task-group>/<task-id>` (or the full `taskKey`) first, so the reader knows which task the reply is about. **Every run-artifact path in this reply MUST be task-qualified** — report the final report as `.okstra/tasks/<task-group>/<task-id>/runs/<task-type>/reports/final-report-<task-type>-<seq>.md` (the full path rooted at the task bundle), NOT the bare `runs/<task-type>/reports/...` form. The bare `runs/...` prefix is byte-for-byte identical across every task-group / task-id of the same task-type, so it cannot tell one task's report from another's — always emit the `.okstra/tasks/<task-group>/<task-id>/` prefix. The same task-qualified rule applies to the team-state path, resume command path, and any other run-artifact path this reply cites.
370
370
 
371
371
  ## Run-scoped pane & teammate lifecycle
372
372
 
@@ -1,8 +1,8 @@
1
1
  """Implementation stage run orchestration.
2
2
 
3
- This module owns the lifecycle for one ``implementation`` stage run: recover
4
- consumer state, select an available Stage Map entry, provision the isolated
5
- stage worktree, and publish the selected stage into run context.
3
+ This module owns the Stage Run Claim for one ``implementation`` stage run:
4
+ recover consumer state, select an available Stage Map entry, provision the
5
+ isolated stage worktree, and record the started event.
6
6
  """
7
7
  from __future__ import annotations
8
8
 
@@ -18,12 +18,12 @@ from .stage_reconcile import auto_reconcile_best_effort
18
18
 
19
19
 
20
20
  class ImplementationStageError(Exception):
21
- """Implementation stage selection or provisioning failed."""
21
+ """Implementation stage claim or provisioning failed."""
22
22
 
23
23
 
24
24
  @dataclass
25
- class StageSelection:
26
- """Resolved implementation stage and its isolated worktree coordinates."""
25
+ class StageRunClaim:
26
+ """Claim over one Stage Map stage and its isolated worktree coordinates."""
27
27
 
28
28
  stage: int
29
29
  worktree_path: str
@@ -50,22 +50,20 @@ def _as_implementation_stage_error(exc: Exception) -> ImplementationStageError:
50
50
  return ImplementationStageError(str(exc))
51
51
 
52
52
 
53
- def select_and_provision_implementation_stage(
53
+ def claim_implementation_stage_run(
54
54
  inp: Any,
55
55
  ctx_stage_map: list[dict[str, Any]],
56
56
  task_group_segment: str,
57
57
  task_id_segment: str,
58
58
  task_key: str,
59
59
  executor_worktree_status: str,
60
- ) -> StageSelection:
61
- """Select a ready implementation stage and provision its stage worktree.
60
+ ) -> StageRunClaim:
61
+ """Claim a ready implementation stage for one run.
62
62
 
63
- The returned selection is path-independent with respect to run artifacts:
63
+ The returned claim is path-independent with respect to run artifacts:
64
64
  callers can compute stage-specific run paths after this function resolves
65
65
  the selected stage.
66
66
  """
67
- del task_key # The registry uses the split identity fields.
68
-
69
67
  from .consumers import backfill_done_from_carry
70
68
  from . import worktree as _worktree
71
69
  from . import worktree_registry as _reg
@@ -95,7 +93,7 @@ def select_and_provision_implementation_stage(
95
93
 
96
94
  if executor_worktree_status.startswith("skipped"):
97
95
  head = _git_out(inp.project_root, "rev-parse", "HEAD")
98
- return StageSelection(
96
+ claim = StageRunClaim(
99
97
  stage=selected,
100
98
  worktree_path="",
101
99
  worktree_branch="",
@@ -105,6 +103,8 @@ def select_and_provision_implementation_stage(
105
103
  started_head_commit=head,
106
104
  concurrent_stages=concurrent_stages,
107
105
  )
106
+ _record_stage_run_claim_started(task_key, plan_run_root, claim)
107
+ return claim
108
108
 
109
109
  # The anchor and multi-dep candidate base must come from the task-key
110
110
  # worktree HEAD (where stage work accumulates), not from inp.project_root
@@ -166,7 +166,7 @@ def select_and_provision_implementation_stage(
166
166
  f"stage worktree provisioning failed: {exc}\n{hint}"
167
167
  ) from exc
168
168
 
169
- return StageSelection(
169
+ claim = StageRunClaim(
170
170
  stage=selected,
171
171
  worktree_path=prov.path,
172
172
  worktree_branch=prov.branch,
@@ -176,22 +176,40 @@ def select_and_provision_implementation_stage(
176
176
  started_head_commit=prov.base_ref,
177
177
  concurrent_stages=concurrent_stages,
178
178
  )
179
+ _record_stage_run_claim_started(task_key, plan_run_root, claim)
180
+ return claim
181
+
182
+
183
+ def _record_stage_run_claim_started(
184
+ task_key: str,
185
+ plan_run_root: Path,
186
+ claim: StageRunClaim,
187
+ ) -> None:
188
+ from .consumers import append_consumer
179
189
 
190
+ now = _dt.datetime.now(_dt.timezone.utc).isoformat()
191
+ append_consumer(
192
+ plan_run_root,
193
+ impl_task_key=task_key,
194
+ stage=claim.stage,
195
+ status="started",
196
+ started_at=now,
197
+ head_commit=claim.started_head_commit,
198
+ )
180
199
 
181
- def apply_implementation_stage(
200
+
201
+ def publish_stage_run_claim(
182
202
  inp: Any,
183
203
  ctx: dict[str, Any],
184
204
  ctx_stage_map: list[dict[str, Any]],
185
- sel: StageSelection,
205
+ claim: StageRunClaim,
186
206
  ) -> None:
187
- """Publish a selected stage into run context and ``consumers.jsonl``."""
188
- from .consumers import append_consumer
189
-
207
+ """Publish a Stage Run Claim into run context."""
190
208
  ctx["parsed_stage_map"] = ctx_stage_map
191
- ctx["effective_stages"] = [sel.stage]
192
- csv = str(sel.stage)
209
+ ctx["effective_stages"] = [claim.stage]
210
+ csv = str(claim.stage)
193
211
  ctx["EFFECTIVE_STAGES"] = csv
194
- ctx["CONCURRENT_RUN_STAGES"] = ",".join(str(s) for s in sel.concurrent_stages)
212
+ ctx["CONCURRENT_RUN_STAGES"] = ",".join(str(s) for s in claim.concurrent_stages)
195
213
  ctx["STAGE_BATCH_DIRECTIVE"] = (
196
214
  f"- **Stage for this implementation run:** `{csv}`. "
197
215
  "Execute exactly this Stage Map stage — this is the authoritative scope. "
@@ -201,20 +219,9 @@ def apply_implementation_stage(
201
219
  inp.stage = csv
202
220
  print(f"selected stages: {csv}", file=sys.stdout)
203
221
 
204
- if sel.worktree_status and not sel.worktree_status.startswith("skipped"):
205
- ctx["EXECUTOR_WORKTREE_PATH"] = sel.worktree_path
206
- ctx["EXECUTOR_WORKTREE_BRANCH"] = sel.worktree_branch
207
- ctx["EXECUTOR_WORKTREE_BASE_REF"] = sel.worktree_base_ref
208
- ctx["EXECUTOR_WORKTREE_STATUS"] = sel.worktree_status
209
- ctx["EXECUTOR_WORKTREE_NOTE"] = sel.worktree_note
210
-
211
- now = _dt.datetime.now(_dt.timezone.utc).isoformat()
212
- plan_run_root = Path(inp.approved_plan_path).resolve().parents[1]
213
- append_consumer(
214
- plan_run_root,
215
- impl_task_key=ctx["TASK_KEY"],
216
- stage=sel.stage,
217
- status="started",
218
- started_at=now,
219
- head_commit=sel.started_head_commit,
220
- )
222
+ if claim.worktree_status and not claim.worktree_status.startswith("skipped"):
223
+ ctx["EXECUTOR_WORKTREE_PATH"] = claim.worktree_path
224
+ ctx["EXECUTOR_WORKTREE_BRANCH"] = claim.worktree_branch
225
+ ctx["EXECUTOR_WORKTREE_BASE_REF"] = claim.worktree_base_ref
226
+ ctx["EXECUTOR_WORKTREE_STATUS"] = claim.worktree_status
227
+ ctx["EXECUTOR_WORKTREE_NOTE"] = claim.worktree_note
@@ -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,
@@ -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