okstra 0.113.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 (348) hide show
  1. package/README.kr.md +2 -2
  2. package/README.md +2 -2
  3. package/docs/for-ai/README.md +2 -2
  4. package/docs/for-ai/skills/{okstra-brief.md → okstra-brief-gen.md} +3 -3
  5. package/docs/for-ai/skills/okstra-inspect.md +1 -1
  6. package/docs/kr/architecture.md +11 -11
  7. package/docs/kr/cli.md +1 -1
  8. package/docs/kr/follow-ups/2026-07-10-final-report-option-3.md +51 -0
  9. package/docs/project-structure-overview.md +2 -2
  10. package/package.json +1 -1
  11. package/runtime/BUILD.json +2 -2
  12. package/runtime/agents/workers/report-writer-worker.md +1 -0
  13. package/runtime/prompts/lead/context-loader.md +2 -2
  14. package/runtime/prompts/lead/okstra-lead-contract.md +1 -1
  15. package/runtime/prompts/profiles/_common-contract.md +3 -3
  16. package/runtime/prompts/profiles/implementation-planning.md +1 -1
  17. package/runtime/prompts/profiles/requirements-discovery.md +2 -2
  18. package/runtime/python/okstra_ctl/fix_cycles.py +1 -1
  19. package/runtime/python/okstra_ctl/implementation_stage.py +46 -39
  20. package/runtime/python/okstra_ctl/report_views.py +151 -12
  21. package/runtime/python/okstra_ctl/run.py +24 -24
  22. package/runtime/python/okstra_ctl/wizard.py +3 -3
  23. package/runtime/python/okstra_ctl/worktree.py +1 -1
  24. package/runtime/python/okstra_token_usage/claude.py +58 -19
  25. package/runtime/python/okstra_token_usage/collect.py +3 -1
  26. package/runtime/python/okstra_vendor/__init__.py +3 -0
  27. package/runtime/schemas/final-report-v1.0.schema.json +20 -0
  28. package/runtime/skills/{okstra-brief → okstra-brief-gen}/SKILL.md +6 -6
  29. package/runtime/skills/{okstra-brief → okstra-brief-gen}/references/reporter-confirmations.md +1 -1
  30. package/runtime/skills/{okstra-brief → okstra-brief-gen}/references/tracker-recursion.md +1 -1
  31. package/runtime/skills/okstra-inspect/SKILL.md +2 -2
  32. package/runtime/templates/reports/brief.template.md +2 -2
  33. package/runtime/templates/reports/final-report.template.md +14 -0
  34. package/runtime/templates/reports/i18n/en.json +23 -12
  35. package/runtime/templates/reports/i18n/ko.json +23 -12
  36. package/runtime/templates/reports/report.css +55 -0
  37. package/runtime/templates/reports/report.js +53 -1
  38. package/runtime/validators/validate-brief.py +5 -5
  39. package/runtime/validators/validate-brief.sh +1 -1
  40. package/src/commands/lifecycle/uninstall.mjs +2 -1
  41. package/src/lib/skill-catalog.mjs +2 -1
  42. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/__init__.py +0 -0
  43. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_approx_clust_coeff.py +0 -41
  44. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_clique.py +0 -112
  45. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_connectivity.py +0 -199
  46. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_density.py +0 -146
  47. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_distance_measures.py +0 -59
  48. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_dominating_set.py +0 -78
  49. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_kcomponents.py +0 -303
  50. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_matching.py +0 -8
  51. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_maxcut.py +0 -94
  52. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_ramsey.py +0 -31
  53. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_steinertree.py +0 -306
  54. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_traveling_salesman.py +0 -1014
  55. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_treewidth.py +0 -274
  56. package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_vertex_cover.py +0 -68
  57. package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/tests/__init__.py +0 -0
  58. package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/tests/base_test.py +0 -81
  59. package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/tests/test_connectivity.py +0 -143
  60. package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/tests/test_correlation.py +0 -122
  61. package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/tests/test_mixing.py +0 -174
  62. package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/tests/test_neighbor_degree.py +0 -107
  63. package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/tests/test_pairs.py +0 -87
  64. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/__init__.py +0 -0
  65. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_basic.py +0 -125
  66. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_centrality.py +0 -192
  67. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_cluster.py +0 -84
  68. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_covering.py +0 -33
  69. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_edgelist.py +0 -240
  70. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_extendability.py +0 -334
  71. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_generators.py +0 -407
  72. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_link_analysis.py +0 -218
  73. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_matching.py +0 -327
  74. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_matrix.py +0 -138
  75. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_project.py +0 -409
  76. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_redundancy.py +0 -35
  77. package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_spectral_bipartivity.py +0 -80
  78. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/__init__.py +0 -0
  79. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_betweenness_centrality.py +0 -923
  80. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_betweenness_centrality_subset.py +0 -354
  81. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_closeness_centrality.py +0 -274
  82. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_current_flow_betweenness_centrality.py +0 -259
  83. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_current_flow_betweenness_centrality_subset.py +0 -147
  84. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_current_flow_closeness.py +0 -43
  85. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_degree_centrality.py +0 -144
  86. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_dispersion.py +0 -73
  87. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_eigenvector_centrality.py +0 -186
  88. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_group.py +0 -277
  89. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_harmonic_centrality.py +0 -122
  90. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_katz_centrality.py +0 -345
  91. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_laplacian_centrality.py +0 -220
  92. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_load_centrality.py +0 -344
  93. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_percolation_centrality.py +0 -87
  94. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_reaching.py +0 -140
  95. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_second_order_centrality.py +0 -82
  96. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_subgraph.py +0 -110
  97. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_trophic.py +0 -302
  98. package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_voterank.py +0 -64
  99. package/runtime/python/okstra_vendor/networkx/algorithms/coloring/tests/__init__.py +0 -0
  100. package/runtime/python/okstra_vendor/networkx/algorithms/coloring/tests/test_coloring.py +0 -863
  101. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/__init__.py +0 -0
  102. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_asyn_fluid.py +0 -147
  103. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_bipartitions.py +0 -157
  104. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_centrality.py +0 -85
  105. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_divisive.py +0 -106
  106. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_kclique.py +0 -91
  107. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_label_propagation.py +0 -241
  108. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_leiden.py +0 -138
  109. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_local.py +0 -76
  110. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_louvain.py +0 -264
  111. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_lukes.py +0 -152
  112. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_modularity_max.py +0 -340
  113. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_quality.py +0 -139
  114. package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_utils.py +0 -26
  115. package/runtime/python/okstra_vendor/networkx/algorithms/components/tests/__init__.py +0 -0
  116. package/runtime/python/okstra_vendor/networkx/algorithms/components/tests/test_attracting.py +0 -70
  117. package/runtime/python/okstra_vendor/networkx/algorithms/components/tests/test_biconnected.py +0 -248
  118. package/runtime/python/okstra_vendor/networkx/algorithms/components/tests/test_connected.py +0 -138
  119. package/runtime/python/okstra_vendor/networkx/algorithms/components/tests/test_semiconnected.py +0 -55
  120. package/runtime/python/okstra_vendor/networkx/algorithms/components/tests/test_strongly_connected.py +0 -193
  121. package/runtime/python/okstra_vendor/networkx/algorithms/components/tests/test_weakly_connected.py +0 -96
  122. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/tests/__init__.py +0 -0
  123. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/tests/test_connectivity.py +0 -421
  124. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/tests/test_cuts.py +0 -309
  125. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/tests/test_disjoint_paths.py +0 -249
  126. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/tests/test_edge_augmentation.py +0 -502
  127. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/tests/test_edge_kcomponents.py +0 -488
  128. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/tests/test_kcomponents.py +0 -323
  129. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/tests/test_kcutsets.py +0 -280
  130. package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/tests/test_stoer_wagner.py +0 -102
  131. package/runtime/python/okstra_vendor/networkx/algorithms/flow/tests/__init__.py +0 -0
  132. package/runtime/python/okstra_vendor/networkx/algorithms/flow/tests/gl1.gpickle.bz2 +0 -0
  133. package/runtime/python/okstra_vendor/networkx/algorithms/flow/tests/gw1.gpickle.bz2 +0 -0
  134. package/runtime/python/okstra_vendor/networkx/algorithms/flow/tests/netgen-2.gpickle.bz2 +0 -0
  135. package/runtime/python/okstra_vendor/networkx/algorithms/flow/tests/test_gomory_hu.py +0 -128
  136. package/runtime/python/okstra_vendor/networkx/algorithms/flow/tests/test_maxflow.py +0 -573
  137. package/runtime/python/okstra_vendor/networkx/algorithms/flow/tests/test_maxflow_large_graph.py +0 -155
  138. package/runtime/python/okstra_vendor/networkx/algorithms/flow/tests/test_mincost.py +0 -475
  139. package/runtime/python/okstra_vendor/networkx/algorithms/flow/tests/test_networksimplex.py +0 -481
  140. package/runtime/python/okstra_vendor/networkx/algorithms/flow/tests/wlm3.gpickle.bz2 +0 -0
  141. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/__init__.py +0 -0
  142. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/iso_r01_s80.A99 +0 -0
  143. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/iso_r01_s80.B99 +0 -0
  144. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/si2_b06_m200.A99 +0 -0
  145. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/si2_b06_m200.B99 +0 -0
  146. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/test_ismags.py +0 -719
  147. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/test_isomorphism.py +0 -103
  148. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/test_isomorphvf2.py +0 -490
  149. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/test_match_helpers.py +0 -64
  150. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/test_temporalisomorphvf2.py +0 -212
  151. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/test_tree_isomorphism.py +0 -202
  152. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/test_vf2pp.py +0 -1655
  153. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/test_vf2pp_helpers.py +0 -3118
  154. package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/test_vf2userfunc.py +0 -196
  155. package/runtime/python/okstra_vendor/networkx/algorithms/link_analysis/tests/__init__.py +0 -0
  156. package/runtime/python/okstra_vendor/networkx/algorithms/link_analysis/tests/test_hits.py +0 -77
  157. package/runtime/python/okstra_vendor/networkx/algorithms/link_analysis/tests/test_pagerank.py +0 -213
  158. package/runtime/python/okstra_vendor/networkx/algorithms/minors/tests/test_contraction.py +0 -544
  159. package/runtime/python/okstra_vendor/networkx/algorithms/operators/tests/__init__.py +0 -0
  160. package/runtime/python/okstra_vendor/networkx/algorithms/operators/tests/test_all.py +0 -328
  161. package/runtime/python/okstra_vendor/networkx/algorithms/operators/tests/test_binary.py +0 -451
  162. package/runtime/python/okstra_vendor/networkx/algorithms/operators/tests/test_product.py +0 -491
  163. package/runtime/python/okstra_vendor/networkx/algorithms/operators/tests/test_unary.py +0 -55
  164. package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/tests/__init__.py +0 -0
  165. package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/tests/test_astar.py +0 -254
  166. package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/tests/test_dense.py +0 -212
  167. package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/tests/test_dense_numpy.py +0 -88
  168. package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/tests/test_generic.py +0 -511
  169. package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/tests/test_unweighted.py +0 -149
  170. package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/tests/test_weighted.py +0 -983
  171. package/runtime/python/okstra_vendor/networkx/algorithms/tests/__init__.py +0 -0
  172. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_asteroidal.py +0 -23
  173. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_boundary.py +0 -154
  174. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_bridges.py +0 -144
  175. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_broadcasting.py +0 -109
  176. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_chains.py +0 -136
  177. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_chordal.py +0 -129
  178. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_clique.py +0 -300
  179. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_cluster.py +0 -678
  180. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_communicability.py +0 -80
  181. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_core.py +0 -266
  182. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_covering.py +0 -85
  183. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_cuts.py +0 -171
  184. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_cycles.py +0 -984
  185. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_d_separation.py +0 -340
  186. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_dag.py +0 -835
  187. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_distance_measures.py +0 -831
  188. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_distance_regular.py +0 -85
  189. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_dominance.py +0 -299
  190. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_dominating.py +0 -115
  191. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_efficiency.py +0 -58
  192. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_euler.py +0 -314
  193. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_graph_hashing.py +0 -872
  194. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_graphical.py +0 -163
  195. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_hierarchy.py +0 -46
  196. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_hybrid.py +0 -24
  197. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_isolate.py +0 -26
  198. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_link_prediction.py +0 -615
  199. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_lowest_common_ancestors.py +0 -459
  200. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_matching.py +0 -556
  201. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_max_weight_clique.py +0 -179
  202. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_mis.py +0 -62
  203. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_moral.py +0 -15
  204. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_node_classification.py +0 -140
  205. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_non_randomness.py +0 -60
  206. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_perfect_graph.py +0 -27
  207. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_planar_drawing.py +0 -274
  208. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_planarity.py +0 -556
  209. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_polynomials.py +0 -57
  210. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_reciprocity.py +0 -37
  211. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_regular.py +0 -88
  212. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_richclub.py +0 -149
  213. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_similarity.py +0 -1158
  214. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_simple_paths.py +0 -803
  215. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_smallworld.py +0 -76
  216. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_smetric.py +0 -8
  217. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_sparsifiers.py +0 -138
  218. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_structuralholes.py +0 -191
  219. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_summarization.py +0 -642
  220. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_swap.py +0 -179
  221. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_threshold.py +0 -270
  222. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_time_dependent.py +0 -431
  223. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_tournament.py +0 -161
  224. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_triads.py +0 -248
  225. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_vitality.py +0 -41
  226. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_voronoi.py +0 -103
  227. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_walks.py +0 -54
  228. package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_wiener.py +0 -157
  229. package/runtime/python/okstra_vendor/networkx/algorithms/traversal/tests/__init__.py +0 -0
  230. package/runtime/python/okstra_vendor/networkx/algorithms/traversal/tests/test_beamsearch.py +0 -25
  231. package/runtime/python/okstra_vendor/networkx/algorithms/traversal/tests/test_bfs.py +0 -203
  232. package/runtime/python/okstra_vendor/networkx/algorithms/traversal/tests/test_dfs.py +0 -307
  233. package/runtime/python/okstra_vendor/networkx/algorithms/traversal/tests/test_edgebfs.py +0 -147
  234. package/runtime/python/okstra_vendor/networkx/algorithms/traversal/tests/test_edgedfs.py +0 -131
  235. package/runtime/python/okstra_vendor/networkx/algorithms/tree/tests/__init__.py +0 -0
  236. package/runtime/python/okstra_vendor/networkx/algorithms/tree/tests/test_branchings.py +0 -624
  237. package/runtime/python/okstra_vendor/networkx/algorithms/tree/tests/test_coding.py +0 -114
  238. package/runtime/python/okstra_vendor/networkx/algorithms/tree/tests/test_decomposition.py +0 -79
  239. package/runtime/python/okstra_vendor/networkx/algorithms/tree/tests/test_distance_measures.py +0 -99
  240. package/runtime/python/okstra_vendor/networkx/algorithms/tree/tests/test_mst.py +0 -934
  241. package/runtime/python/okstra_vendor/networkx/algorithms/tree/tests/test_operations.py +0 -53
  242. package/runtime/python/okstra_vendor/networkx/algorithms/tree/tests/test_recognition.py +0 -174
  243. package/runtime/python/okstra_vendor/networkx/classes/tests/__init__.py +0 -0
  244. package/runtime/python/okstra_vendor/networkx/classes/tests/dispatch_interface.py +0 -192
  245. package/runtime/python/okstra_vendor/networkx/classes/tests/historical_tests.py +0 -476
  246. package/runtime/python/okstra_vendor/networkx/classes/tests/test_coreviews.py +0 -362
  247. package/runtime/python/okstra_vendor/networkx/classes/tests/test_digraph.py +0 -331
  248. package/runtime/python/okstra_vendor/networkx/classes/tests/test_digraph_historical.py +0 -110
  249. package/runtime/python/okstra_vendor/networkx/classes/tests/test_filters.py +0 -177
  250. package/runtime/python/okstra_vendor/networkx/classes/tests/test_function.py +0 -1045
  251. package/runtime/python/okstra_vendor/networkx/classes/tests/test_graph.py +0 -950
  252. package/runtime/python/okstra_vendor/networkx/classes/tests/test_graph_historical.py +0 -12
  253. package/runtime/python/okstra_vendor/networkx/classes/tests/test_graphviews.py +0 -349
  254. package/runtime/python/okstra_vendor/networkx/classes/tests/test_multidigraph.py +0 -459
  255. package/runtime/python/okstra_vendor/networkx/classes/tests/test_multigraph.py +0 -528
  256. package/runtime/python/okstra_vendor/networkx/classes/tests/test_reportviews.py +0 -1421
  257. package/runtime/python/okstra_vendor/networkx/classes/tests/test_special.py +0 -131
  258. package/runtime/python/okstra_vendor/networkx/classes/tests/test_subgraphviews.py +0 -371
  259. package/runtime/python/okstra_vendor/networkx/conftest.py +0 -261
  260. package/runtime/python/okstra_vendor/networkx/drawing/tests/__init__.py +0 -0
  261. package/runtime/python/okstra_vendor/networkx/drawing/tests/baseline/test_display_complex.png +0 -0
  262. package/runtime/python/okstra_vendor/networkx/drawing/tests/baseline/test_display_empty_graph.png +0 -0
  263. package/runtime/python/okstra_vendor/networkx/drawing/tests/baseline/test_display_house_with_colors.png +0 -0
  264. package/runtime/python/okstra_vendor/networkx/drawing/tests/baseline/test_display_labels_and_colors.png +0 -0
  265. package/runtime/python/okstra_vendor/networkx/drawing/tests/baseline/test_display_shortest_path.png +0 -0
  266. package/runtime/python/okstra_vendor/networkx/drawing/tests/baseline/test_house_with_colors.png +0 -0
  267. package/runtime/python/okstra_vendor/networkx/drawing/tests/test_agraph.py +0 -237
  268. package/runtime/python/okstra_vendor/networkx/drawing/tests/test_image_comparison_pylab_mpl.py +0 -229
  269. package/runtime/python/okstra_vendor/networkx/drawing/tests/test_latex.py +0 -285
  270. package/runtime/python/okstra_vendor/networkx/drawing/tests/test_layout.py +0 -631
  271. package/runtime/python/okstra_vendor/networkx/drawing/tests/test_pydot.py +0 -146
  272. package/runtime/python/okstra_vendor/networkx/drawing/tests/test_pylab.py +0 -1582
  273. package/runtime/python/okstra_vendor/networkx/generators/tests/__init__.py +0 -0
  274. package/runtime/python/okstra_vendor/networkx/generators/tests/test_atlas.py +0 -75
  275. package/runtime/python/okstra_vendor/networkx/generators/tests/test_classic.py +0 -642
  276. package/runtime/python/okstra_vendor/networkx/generators/tests/test_cographs.py +0 -20
  277. package/runtime/python/okstra_vendor/networkx/generators/tests/test_community.py +0 -362
  278. package/runtime/python/okstra_vendor/networkx/generators/tests/test_degree_seq.py +0 -224
  279. package/runtime/python/okstra_vendor/networkx/generators/tests/test_directed.py +0 -189
  280. package/runtime/python/okstra_vendor/networkx/generators/tests/test_duplication.py +0 -103
  281. package/runtime/python/okstra_vendor/networkx/generators/tests/test_ego.py +0 -39
  282. package/runtime/python/okstra_vendor/networkx/generators/tests/test_expanders.py +0 -182
  283. package/runtime/python/okstra_vendor/networkx/generators/tests/test_geometric.py +0 -488
  284. package/runtime/python/okstra_vendor/networkx/generators/tests/test_harary_graph.py +0 -133
  285. package/runtime/python/okstra_vendor/networkx/generators/tests/test_internet_as_graphs.py +0 -221
  286. package/runtime/python/okstra_vendor/networkx/generators/tests/test_intersection.py +0 -28
  287. package/runtime/python/okstra_vendor/networkx/generators/tests/test_interval_graph.py +0 -144
  288. package/runtime/python/okstra_vendor/networkx/generators/tests/test_joint_degree_seq.py +0 -125
  289. package/runtime/python/okstra_vendor/networkx/generators/tests/test_lattice.py +0 -264
  290. package/runtime/python/okstra_vendor/networkx/generators/tests/test_line.py +0 -316
  291. package/runtime/python/okstra_vendor/networkx/generators/tests/test_mycielski.py +0 -30
  292. package/runtime/python/okstra_vendor/networkx/generators/tests/test_nonisomorphic_trees.py +0 -82
  293. package/runtime/python/okstra_vendor/networkx/generators/tests/test_random_clustered.py +0 -33
  294. package/runtime/python/okstra_vendor/networkx/generators/tests/test_random_graphs.py +0 -495
  295. package/runtime/python/okstra_vendor/networkx/generators/tests/test_small.py +0 -220
  296. package/runtime/python/okstra_vendor/networkx/generators/tests/test_spectral_graph_forge.py +0 -49
  297. package/runtime/python/okstra_vendor/networkx/generators/tests/test_stochastic.py +0 -72
  298. package/runtime/python/okstra_vendor/networkx/generators/tests/test_sudoku.py +0 -92
  299. package/runtime/python/okstra_vendor/networkx/generators/tests/test_time_series.py +0 -64
  300. package/runtime/python/okstra_vendor/networkx/generators/tests/test_trees.py +0 -195
  301. package/runtime/python/okstra_vendor/networkx/generators/tests/test_triads.py +0 -15
  302. package/runtime/python/okstra_vendor/networkx/linalg/tests/__init__.py +0 -0
  303. package/runtime/python/okstra_vendor/networkx/linalg/tests/test_algebraic_connectivity.py +0 -400
  304. package/runtime/python/okstra_vendor/networkx/linalg/tests/test_attrmatrix.py +0 -108
  305. package/runtime/python/okstra_vendor/networkx/linalg/tests/test_bethehessian.py +0 -40
  306. package/runtime/python/okstra_vendor/networkx/linalg/tests/test_graphmatrix.py +0 -275
  307. package/runtime/python/okstra_vendor/networkx/linalg/tests/test_laplacian.py +0 -334
  308. package/runtime/python/okstra_vendor/networkx/linalg/tests/test_modularity.py +0 -86
  309. package/runtime/python/okstra_vendor/networkx/linalg/tests/test_spectrum.py +0 -70
  310. package/runtime/python/okstra_vendor/networkx/readwrite/json_graph/tests/__init__.py +0 -0
  311. package/runtime/python/okstra_vendor/networkx/readwrite/json_graph/tests/test_adjacency.py +0 -78
  312. package/runtime/python/okstra_vendor/networkx/readwrite/json_graph/tests/test_cytoscape.py +0 -78
  313. package/runtime/python/okstra_vendor/networkx/readwrite/json_graph/tests/test_node_link.py +0 -109
  314. package/runtime/python/okstra_vendor/networkx/readwrite/json_graph/tests/test_tree.py +0 -48
  315. package/runtime/python/okstra_vendor/networkx/readwrite/tests/__init__.py +0 -0
  316. package/runtime/python/okstra_vendor/networkx/readwrite/tests/test_adjlist.py +0 -354
  317. package/runtime/python/okstra_vendor/networkx/readwrite/tests/test_edgelist.py +0 -318
  318. package/runtime/python/okstra_vendor/networkx/readwrite/tests/test_gexf.py +0 -612
  319. package/runtime/python/okstra_vendor/networkx/readwrite/tests/test_gml.py +0 -744
  320. package/runtime/python/okstra_vendor/networkx/readwrite/tests/test_graph6.py +0 -181
  321. package/runtime/python/okstra_vendor/networkx/readwrite/tests/test_graphml.py +0 -1531
  322. package/runtime/python/okstra_vendor/networkx/readwrite/tests/test_leda.py +0 -30
  323. package/runtime/python/okstra_vendor/networkx/readwrite/tests/test_p2g.py +0 -63
  324. package/runtime/python/okstra_vendor/networkx/readwrite/tests/test_pajek.py +0 -128
  325. package/runtime/python/okstra_vendor/networkx/readwrite/tests/test_sparse6.py +0 -166
  326. package/runtime/python/okstra_vendor/networkx/readwrite/tests/test_text.py +0 -1742
  327. package/runtime/python/okstra_vendor/networkx/tests/__init__.py +0 -0
  328. package/runtime/python/okstra_vendor/networkx/tests/test_all_random_functions.py +0 -248
  329. package/runtime/python/okstra_vendor/networkx/tests/test_convert.py +0 -321
  330. package/runtime/python/okstra_vendor/networkx/tests/test_convert_numpy.py +0 -531
  331. package/runtime/python/okstra_vendor/networkx/tests/test_convert_pandas.py +0 -349
  332. package/runtime/python/okstra_vendor/networkx/tests/test_convert_scipy.py +0 -281
  333. package/runtime/python/okstra_vendor/networkx/tests/test_exceptions.py +0 -40
  334. package/runtime/python/okstra_vendor/networkx/tests/test_import.py +0 -11
  335. package/runtime/python/okstra_vendor/networkx/tests/test_lazy_imports.py +0 -96
  336. package/runtime/python/okstra_vendor/networkx/tests/test_relabel.py +0 -349
  337. package/runtime/python/okstra_vendor/networkx/tests/test_removed_functions_exception_messages.py +0 -8
  338. package/runtime/python/okstra_vendor/networkx/utils/tests/__init__.py +0 -0
  339. package/runtime/python/okstra_vendor/networkx/utils/tests/test__init.py +0 -11
  340. package/runtime/python/okstra_vendor/networkx/utils/tests/test_backends.py +0 -225
  341. package/runtime/python/okstra_vendor/networkx/utils/tests/test_config.py +0 -263
  342. package/runtime/python/okstra_vendor/networkx/utils/tests/test_decorators.py +0 -510
  343. package/runtime/python/okstra_vendor/networkx/utils/tests/test_heaps.py +0 -131
  344. package/runtime/python/okstra_vendor/networkx/utils/tests/test_mapped_queue.py +0 -268
  345. package/runtime/python/okstra_vendor/networkx/utils/tests/test_misc.py +0 -393
  346. package/runtime/python/okstra_vendor/networkx/utils/tests/test_random_sequence.py +0 -53
  347. package/runtime/python/okstra_vendor/networkx/utils/tests/test_rcm.py +0 -63
  348. 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,
@@ -209,7 +209,7 @@ def _brief_suggestions(path: Path) -> tuple[str, str]:
209
209
 
210
210
  - ``task_group`` ← frontmatter ``task-group``.
211
211
  - ``task_id`` ← frontmatter ``brief-id`` (which matches the
212
- filename stem in okstra-brief output and is the
212
+ filename stem in okstra-brief-gen output and is the
213
213
  strongest single identifier of the task).
214
214
 
215
215
  A brief without frontmatter, or with placeholder values, yields two
@@ -1261,9 +1261,9 @@ def _recently_used_brief_times(state: WizardState) -> dict[str, float]:
1261
1261
 
1262
1262
 
1263
1263
  def _suggest_group_briefs(state: WizardState, limit: int = 6) -> list[str]:
1264
- """Return recent okstra-brief outputs for the selected task-group.
1264
+ """Return recent okstra-brief-gen outputs for the selected task-group.
1265
1265
 
1266
- ``okstra-brief`` writes to ``.okstra/briefs/<task-group>/**/*.md``. The
1266
+ ``okstra-brief-gen`` writes to ``.okstra/briefs/<task-group>/**/*.md``. The
1267
1267
  wizard exposes those paths after task-group selection so users can pick a
1268
1268
  generated brief instead of typing its path. Paths are project-relative.
1269
1269
  """
@@ -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.",
@@ -1,9 +1,9 @@
1
1
  ---
2
- name: okstra-brief
2
+ name: okstra-brief-gen
3
3
  description: Use when the user wants to generate a task brief file for okstra from a requirements document, an existing markdown file, an issue-tracker ticket (Linear / Jira / GitHub / Notion), a link URL, conversation context, or short user input. Produces the markdown brief consumed by `okstra-run` Step 5 (task-brief). Trigger words include "okstra brief", "make a brief", "brief 생성", "요구사항 brief 만들어", "okstra 입력 만들어", "task brief 작성", "이 티켓으로 brief", "이 링크로 brief".
4
4
  ---
5
5
 
6
- # okstra-brief
6
+ # okstra-brief-gen
7
7
 
8
8
  Generate a single `task brief` markdown file under the okstra project domain
9
9
  (`.okstra/briefs/<task-group>/<task-id>.md`) so it can be fed to
@@ -41,21 +41,21 @@ cross-verification and would conflict with anything decided here.
41
41
  ## Intended chain
42
42
 
43
43
  ```
44
- okstra-brief (reporter-input variant)
44
+ okstra-brief-gen (reporter-input variant)
45
45
  → okstra-run (requirements-discovery | error-analysis)
46
46
  → okstra-run (implementation-planning)
47
47
  → okstra-run (implementation)
48
48
  → okstra-run (final-verification)
49
49
  → okstra-run (release-handoff)
50
50
 
51
- okstra-brief (codebase-scan variant)
51
+ okstra-brief-gen (codebase-scan variant)
52
52
  → okstra-run (improvement-discovery)
53
53
  → okstra-run (implementation-planning)
54
54
  → okstra-run (implementation)
55
55
  → okstra-run (final-verification)
56
56
  → okstra-run (release-handoff)
57
57
 
58
- okstra-brief (error-feedback variant)
58
+ okstra-brief-gen (error-feedback variant)
59
59
  → okstra-run (error-analysis)
60
60
  → okstra-run (implementation-planning)
61
61
  → okstra-run (implementation)
@@ -737,7 +737,7 @@ The installed template `~/.okstra/templates/reports/brief.template.md` is the by
737
737
  notion | url | user-input`).
738
738
  - `task-group` echoes the Step 2a value.
739
739
  - `created` is the brief's creation date (`YYYY-MM-DD`).
740
- - `generator: okstra-brief` is a fixed value.
740
+ - `generator: okstra-brief-gen` is a fixed value.
741
741
  - `reporter-confirmations` is set by Step 6.5 (`complete | partial | pending |
742
742
  skipped`).
743
743
  - `scope` is `codebase` for the codebase-scan variant; omit it (⇒
@@ -18,7 +18,7 @@ rows, sort by priority and ask only the top 12 this round:
18
18
 
19
19
  Any rows beyond the cap are left unanswered in this run; set
20
20
  `reporter-confirmations: partial` in the frontmatter and tell the user which
21
- rows remain. The next `okstra-brief` re-run (or the downstream phase that
21
+ rows remain. The next `okstra-brief-gen` re-run (or the downstream phase that
22
22
  consumes `Blocks=next-phase`) handles the remainder.
23
23
 
24
24
  Each question carries:
@@ -1,7 +1,7 @@
1
1
  # Sub-ticket / child-issue recursion (tracker sources)
2
2
 
3
3
  Read this only when a fetched tracker ticket shows one or more child references
4
- (okstra-brief Step 1b sub-step 6). It defines how the child tree is discovered,
4
+ (okstra-brief-gen Step 1b sub-step 6). It defines how the child tree is discovered,
5
5
  which briefs are emitted, and how re-runs stay idempotent.
6
6
 
7
7
  ## 1. Discover child references per tracker