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.
- package/docs/kr/architecture.md +2 -2
- package/docs/kr/follow-ups/2026-07-10-final-report-option-3.md +51 -0
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/workers/report-writer-worker.md +1 -0
- package/runtime/prompts/lead/okstra-lead-contract.md +1 -1
- package/runtime/python/okstra_ctl/implementation_stage.py +46 -39
- package/runtime/python/okstra_ctl/report_views.py +151 -12
- package/runtime/python/okstra_ctl/run.py +24 -24
- package/runtime/python/okstra_ctl/worktree.py +1 -1
- package/runtime/python/okstra_token_usage/claude.py +58 -19
- package/runtime/python/okstra_token_usage/collect.py +3 -1
- package/runtime/python/okstra_vendor/__init__.py +3 -0
- package/runtime/schemas/final-report-v1.0.schema.json +20 -0
- package/runtime/templates/reports/final-report.template.md +14 -0
- package/runtime/templates/reports/i18n/en.json +23 -12
- package/runtime/templates/reports/i18n/ko.json +23 -12
- package/runtime/templates/reports/report.css +55 -0
- package/runtime/templates/reports/report.js +53 -1
- package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/__init__.py +0 -0
- package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_approx_clust_coeff.py +0 -41
- package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_clique.py +0 -112
- package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_connectivity.py +0 -199
- package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_density.py +0 -146
- package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_distance_measures.py +0 -59
- package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_dominating_set.py +0 -78
- package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_kcomponents.py +0 -303
- package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_matching.py +0 -8
- package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_maxcut.py +0 -94
- package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_ramsey.py +0 -31
- package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_steinertree.py +0 -306
- package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_traveling_salesman.py +0 -1014
- package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_treewidth.py +0 -274
- package/runtime/python/okstra_vendor/networkx/algorithms/approximation/tests/test_vertex_cover.py +0 -68
- package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/tests/__init__.py +0 -0
- package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/tests/base_test.py +0 -81
- package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/tests/test_connectivity.py +0 -143
- package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/tests/test_correlation.py +0 -122
- package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/tests/test_mixing.py +0 -174
- package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/tests/test_neighbor_degree.py +0 -107
- package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/tests/test_pairs.py +0 -87
- package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/__init__.py +0 -0
- package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_basic.py +0 -125
- package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_centrality.py +0 -192
- package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_cluster.py +0 -84
- package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_covering.py +0 -33
- package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_edgelist.py +0 -240
- package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_extendability.py +0 -334
- package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_generators.py +0 -407
- package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_link_analysis.py +0 -218
- package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_matching.py +0 -327
- package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_matrix.py +0 -138
- package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_project.py +0 -409
- package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_redundancy.py +0 -35
- package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/tests/test_spectral_bipartivity.py +0 -80
- package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/__init__.py +0 -0
- package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_betweenness_centrality.py +0 -923
- package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_betweenness_centrality_subset.py +0 -354
- package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_closeness_centrality.py +0 -274
- package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_current_flow_betweenness_centrality.py +0 -259
- package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_current_flow_betweenness_centrality_subset.py +0 -147
- package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_current_flow_closeness.py +0 -43
- package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_degree_centrality.py +0 -144
- package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_dispersion.py +0 -73
- package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_eigenvector_centrality.py +0 -186
- package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_group.py +0 -277
- package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_harmonic_centrality.py +0 -122
- package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_katz_centrality.py +0 -345
- package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_laplacian_centrality.py +0 -220
- package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_load_centrality.py +0 -344
- package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_percolation_centrality.py +0 -87
- package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_reaching.py +0 -140
- package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_second_order_centrality.py +0 -82
- package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_subgraph.py +0 -110
- package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_trophic.py +0 -302
- package/runtime/python/okstra_vendor/networkx/algorithms/centrality/tests/test_voterank.py +0 -64
- package/runtime/python/okstra_vendor/networkx/algorithms/coloring/tests/__init__.py +0 -0
- package/runtime/python/okstra_vendor/networkx/algorithms/coloring/tests/test_coloring.py +0 -863
- package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/__init__.py +0 -0
- package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_asyn_fluid.py +0 -147
- package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_bipartitions.py +0 -157
- package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_centrality.py +0 -85
- package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_divisive.py +0 -106
- package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_kclique.py +0 -91
- package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_label_propagation.py +0 -241
- package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_leiden.py +0 -138
- package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_local.py +0 -76
- package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_louvain.py +0 -264
- package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_lukes.py +0 -152
- package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_modularity_max.py +0 -340
- package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_quality.py +0 -139
- package/runtime/python/okstra_vendor/networkx/algorithms/community/tests/test_utils.py +0 -26
- package/runtime/python/okstra_vendor/networkx/algorithms/components/tests/__init__.py +0 -0
- package/runtime/python/okstra_vendor/networkx/algorithms/components/tests/test_attracting.py +0 -70
- package/runtime/python/okstra_vendor/networkx/algorithms/components/tests/test_biconnected.py +0 -248
- package/runtime/python/okstra_vendor/networkx/algorithms/components/tests/test_connected.py +0 -138
- package/runtime/python/okstra_vendor/networkx/algorithms/components/tests/test_semiconnected.py +0 -55
- package/runtime/python/okstra_vendor/networkx/algorithms/components/tests/test_strongly_connected.py +0 -193
- package/runtime/python/okstra_vendor/networkx/algorithms/components/tests/test_weakly_connected.py +0 -96
- package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/tests/__init__.py +0 -0
- package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/tests/test_connectivity.py +0 -421
- package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/tests/test_cuts.py +0 -309
- package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/tests/test_disjoint_paths.py +0 -249
- package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/tests/test_edge_augmentation.py +0 -502
- package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/tests/test_edge_kcomponents.py +0 -488
- package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/tests/test_kcomponents.py +0 -323
- package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/tests/test_kcutsets.py +0 -280
- package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/tests/test_stoer_wagner.py +0 -102
- package/runtime/python/okstra_vendor/networkx/algorithms/flow/tests/__init__.py +0 -0
- package/runtime/python/okstra_vendor/networkx/algorithms/flow/tests/gl1.gpickle.bz2 +0 -0
- package/runtime/python/okstra_vendor/networkx/algorithms/flow/tests/gw1.gpickle.bz2 +0 -0
- package/runtime/python/okstra_vendor/networkx/algorithms/flow/tests/netgen-2.gpickle.bz2 +0 -0
- package/runtime/python/okstra_vendor/networkx/algorithms/flow/tests/test_gomory_hu.py +0 -128
- package/runtime/python/okstra_vendor/networkx/algorithms/flow/tests/test_maxflow.py +0 -573
- package/runtime/python/okstra_vendor/networkx/algorithms/flow/tests/test_maxflow_large_graph.py +0 -155
- package/runtime/python/okstra_vendor/networkx/algorithms/flow/tests/test_mincost.py +0 -475
- package/runtime/python/okstra_vendor/networkx/algorithms/flow/tests/test_networksimplex.py +0 -481
- package/runtime/python/okstra_vendor/networkx/algorithms/flow/tests/wlm3.gpickle.bz2 +0 -0
- package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/__init__.py +0 -0
- package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/iso_r01_s80.A99 +0 -0
- package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/iso_r01_s80.B99 +0 -0
- package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/si2_b06_m200.A99 +0 -0
- package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/si2_b06_m200.B99 +0 -0
- package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/test_ismags.py +0 -719
- package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/test_isomorphism.py +0 -103
- package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/test_isomorphvf2.py +0 -490
- package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/test_match_helpers.py +0 -64
- package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/test_temporalisomorphvf2.py +0 -212
- package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/test_tree_isomorphism.py +0 -202
- package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/test_vf2pp.py +0 -1655
- package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/test_vf2pp_helpers.py +0 -3118
- package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tests/test_vf2userfunc.py +0 -196
- package/runtime/python/okstra_vendor/networkx/algorithms/link_analysis/tests/__init__.py +0 -0
- package/runtime/python/okstra_vendor/networkx/algorithms/link_analysis/tests/test_hits.py +0 -77
- package/runtime/python/okstra_vendor/networkx/algorithms/link_analysis/tests/test_pagerank.py +0 -213
- package/runtime/python/okstra_vendor/networkx/algorithms/minors/tests/test_contraction.py +0 -544
- package/runtime/python/okstra_vendor/networkx/algorithms/operators/tests/__init__.py +0 -0
- package/runtime/python/okstra_vendor/networkx/algorithms/operators/tests/test_all.py +0 -328
- package/runtime/python/okstra_vendor/networkx/algorithms/operators/tests/test_binary.py +0 -451
- package/runtime/python/okstra_vendor/networkx/algorithms/operators/tests/test_product.py +0 -491
- package/runtime/python/okstra_vendor/networkx/algorithms/operators/tests/test_unary.py +0 -55
- package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/tests/__init__.py +0 -0
- package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/tests/test_astar.py +0 -254
- package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/tests/test_dense.py +0 -212
- package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/tests/test_dense_numpy.py +0 -88
- package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/tests/test_generic.py +0 -511
- package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/tests/test_unweighted.py +0 -149
- package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/tests/test_weighted.py +0 -983
- package/runtime/python/okstra_vendor/networkx/algorithms/tests/__init__.py +0 -0
- package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_asteroidal.py +0 -23
- package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_boundary.py +0 -154
- package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_bridges.py +0 -144
- package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_broadcasting.py +0 -109
- package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_chains.py +0 -136
- package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_chordal.py +0 -129
- package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_clique.py +0 -300
- package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_cluster.py +0 -678
- package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_communicability.py +0 -80
- package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_core.py +0 -266
- package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_covering.py +0 -85
- package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_cuts.py +0 -171
- package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_cycles.py +0 -984
- package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_d_separation.py +0 -340
- package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_dag.py +0 -835
- package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_distance_measures.py +0 -831
- package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_distance_regular.py +0 -85
- package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_dominance.py +0 -299
- package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_dominating.py +0 -115
- package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_efficiency.py +0 -58
- package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_euler.py +0 -314
- package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_graph_hashing.py +0 -872
- package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_graphical.py +0 -163
- package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_hierarchy.py +0 -46
- package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_hybrid.py +0 -24
- package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_isolate.py +0 -26
- package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_link_prediction.py +0 -615
- package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_lowest_common_ancestors.py +0 -459
- package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_matching.py +0 -556
- package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_max_weight_clique.py +0 -179
- package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_mis.py +0 -62
- package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_moral.py +0 -15
- package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_node_classification.py +0 -140
- package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_non_randomness.py +0 -60
- package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_perfect_graph.py +0 -27
- package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_planar_drawing.py +0 -274
- package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_planarity.py +0 -556
- package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_polynomials.py +0 -57
- package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_reciprocity.py +0 -37
- package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_regular.py +0 -88
- package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_richclub.py +0 -149
- package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_similarity.py +0 -1158
- package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_simple_paths.py +0 -803
- package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_smallworld.py +0 -76
- package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_smetric.py +0 -8
- package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_sparsifiers.py +0 -138
- package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_structuralholes.py +0 -191
- package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_summarization.py +0 -642
- package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_swap.py +0 -179
- package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_threshold.py +0 -270
- package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_time_dependent.py +0 -431
- package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_tournament.py +0 -161
- package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_triads.py +0 -248
- package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_vitality.py +0 -41
- package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_voronoi.py +0 -103
- package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_walks.py +0 -54
- package/runtime/python/okstra_vendor/networkx/algorithms/tests/test_wiener.py +0 -157
- package/runtime/python/okstra_vendor/networkx/algorithms/traversal/tests/__init__.py +0 -0
- package/runtime/python/okstra_vendor/networkx/algorithms/traversal/tests/test_beamsearch.py +0 -25
- package/runtime/python/okstra_vendor/networkx/algorithms/traversal/tests/test_bfs.py +0 -203
- package/runtime/python/okstra_vendor/networkx/algorithms/traversal/tests/test_dfs.py +0 -307
- package/runtime/python/okstra_vendor/networkx/algorithms/traversal/tests/test_edgebfs.py +0 -147
- package/runtime/python/okstra_vendor/networkx/algorithms/traversal/tests/test_edgedfs.py +0 -131
- package/runtime/python/okstra_vendor/networkx/algorithms/tree/tests/__init__.py +0 -0
- package/runtime/python/okstra_vendor/networkx/algorithms/tree/tests/test_branchings.py +0 -624
- package/runtime/python/okstra_vendor/networkx/algorithms/tree/tests/test_coding.py +0 -114
- package/runtime/python/okstra_vendor/networkx/algorithms/tree/tests/test_decomposition.py +0 -79
- package/runtime/python/okstra_vendor/networkx/algorithms/tree/tests/test_distance_measures.py +0 -99
- package/runtime/python/okstra_vendor/networkx/algorithms/tree/tests/test_mst.py +0 -934
- package/runtime/python/okstra_vendor/networkx/algorithms/tree/tests/test_operations.py +0 -53
- package/runtime/python/okstra_vendor/networkx/algorithms/tree/tests/test_recognition.py +0 -174
- package/runtime/python/okstra_vendor/networkx/classes/tests/__init__.py +0 -0
- package/runtime/python/okstra_vendor/networkx/classes/tests/dispatch_interface.py +0 -192
- package/runtime/python/okstra_vendor/networkx/classes/tests/historical_tests.py +0 -476
- package/runtime/python/okstra_vendor/networkx/classes/tests/test_coreviews.py +0 -362
- package/runtime/python/okstra_vendor/networkx/classes/tests/test_digraph.py +0 -331
- package/runtime/python/okstra_vendor/networkx/classes/tests/test_digraph_historical.py +0 -110
- package/runtime/python/okstra_vendor/networkx/classes/tests/test_filters.py +0 -177
- package/runtime/python/okstra_vendor/networkx/classes/tests/test_function.py +0 -1045
- package/runtime/python/okstra_vendor/networkx/classes/tests/test_graph.py +0 -950
- package/runtime/python/okstra_vendor/networkx/classes/tests/test_graph_historical.py +0 -12
- package/runtime/python/okstra_vendor/networkx/classes/tests/test_graphviews.py +0 -349
- package/runtime/python/okstra_vendor/networkx/classes/tests/test_multidigraph.py +0 -459
- package/runtime/python/okstra_vendor/networkx/classes/tests/test_multigraph.py +0 -528
- package/runtime/python/okstra_vendor/networkx/classes/tests/test_reportviews.py +0 -1421
- package/runtime/python/okstra_vendor/networkx/classes/tests/test_special.py +0 -131
- package/runtime/python/okstra_vendor/networkx/classes/tests/test_subgraphviews.py +0 -371
- package/runtime/python/okstra_vendor/networkx/conftest.py +0 -261
- package/runtime/python/okstra_vendor/networkx/drawing/tests/__init__.py +0 -0
- package/runtime/python/okstra_vendor/networkx/drawing/tests/baseline/test_display_complex.png +0 -0
- package/runtime/python/okstra_vendor/networkx/drawing/tests/baseline/test_display_empty_graph.png +0 -0
- package/runtime/python/okstra_vendor/networkx/drawing/tests/baseline/test_display_house_with_colors.png +0 -0
- package/runtime/python/okstra_vendor/networkx/drawing/tests/baseline/test_display_labels_and_colors.png +0 -0
- package/runtime/python/okstra_vendor/networkx/drawing/tests/baseline/test_display_shortest_path.png +0 -0
- package/runtime/python/okstra_vendor/networkx/drawing/tests/baseline/test_house_with_colors.png +0 -0
- package/runtime/python/okstra_vendor/networkx/drawing/tests/test_agraph.py +0 -237
- package/runtime/python/okstra_vendor/networkx/drawing/tests/test_image_comparison_pylab_mpl.py +0 -229
- package/runtime/python/okstra_vendor/networkx/drawing/tests/test_latex.py +0 -285
- package/runtime/python/okstra_vendor/networkx/drawing/tests/test_layout.py +0 -631
- package/runtime/python/okstra_vendor/networkx/drawing/tests/test_pydot.py +0 -146
- package/runtime/python/okstra_vendor/networkx/drawing/tests/test_pylab.py +0 -1582
- package/runtime/python/okstra_vendor/networkx/generators/tests/__init__.py +0 -0
- package/runtime/python/okstra_vendor/networkx/generators/tests/test_atlas.py +0 -75
- package/runtime/python/okstra_vendor/networkx/generators/tests/test_classic.py +0 -642
- package/runtime/python/okstra_vendor/networkx/generators/tests/test_cographs.py +0 -20
- package/runtime/python/okstra_vendor/networkx/generators/tests/test_community.py +0 -362
- package/runtime/python/okstra_vendor/networkx/generators/tests/test_degree_seq.py +0 -224
- package/runtime/python/okstra_vendor/networkx/generators/tests/test_directed.py +0 -189
- package/runtime/python/okstra_vendor/networkx/generators/tests/test_duplication.py +0 -103
- package/runtime/python/okstra_vendor/networkx/generators/tests/test_ego.py +0 -39
- package/runtime/python/okstra_vendor/networkx/generators/tests/test_expanders.py +0 -182
- package/runtime/python/okstra_vendor/networkx/generators/tests/test_geometric.py +0 -488
- package/runtime/python/okstra_vendor/networkx/generators/tests/test_harary_graph.py +0 -133
- package/runtime/python/okstra_vendor/networkx/generators/tests/test_internet_as_graphs.py +0 -221
- package/runtime/python/okstra_vendor/networkx/generators/tests/test_intersection.py +0 -28
- package/runtime/python/okstra_vendor/networkx/generators/tests/test_interval_graph.py +0 -144
- package/runtime/python/okstra_vendor/networkx/generators/tests/test_joint_degree_seq.py +0 -125
- package/runtime/python/okstra_vendor/networkx/generators/tests/test_lattice.py +0 -264
- package/runtime/python/okstra_vendor/networkx/generators/tests/test_line.py +0 -316
- package/runtime/python/okstra_vendor/networkx/generators/tests/test_mycielski.py +0 -30
- package/runtime/python/okstra_vendor/networkx/generators/tests/test_nonisomorphic_trees.py +0 -82
- package/runtime/python/okstra_vendor/networkx/generators/tests/test_random_clustered.py +0 -33
- package/runtime/python/okstra_vendor/networkx/generators/tests/test_random_graphs.py +0 -495
- package/runtime/python/okstra_vendor/networkx/generators/tests/test_small.py +0 -220
- package/runtime/python/okstra_vendor/networkx/generators/tests/test_spectral_graph_forge.py +0 -49
- package/runtime/python/okstra_vendor/networkx/generators/tests/test_stochastic.py +0 -72
- package/runtime/python/okstra_vendor/networkx/generators/tests/test_sudoku.py +0 -92
- package/runtime/python/okstra_vendor/networkx/generators/tests/test_time_series.py +0 -64
- package/runtime/python/okstra_vendor/networkx/generators/tests/test_trees.py +0 -195
- package/runtime/python/okstra_vendor/networkx/generators/tests/test_triads.py +0 -15
- package/runtime/python/okstra_vendor/networkx/linalg/tests/__init__.py +0 -0
- package/runtime/python/okstra_vendor/networkx/linalg/tests/test_algebraic_connectivity.py +0 -400
- package/runtime/python/okstra_vendor/networkx/linalg/tests/test_attrmatrix.py +0 -108
- package/runtime/python/okstra_vendor/networkx/linalg/tests/test_bethehessian.py +0 -40
- package/runtime/python/okstra_vendor/networkx/linalg/tests/test_graphmatrix.py +0 -275
- package/runtime/python/okstra_vendor/networkx/linalg/tests/test_laplacian.py +0 -334
- package/runtime/python/okstra_vendor/networkx/linalg/tests/test_modularity.py +0 -86
- package/runtime/python/okstra_vendor/networkx/linalg/tests/test_spectrum.py +0 -70
- package/runtime/python/okstra_vendor/networkx/readwrite/json_graph/tests/__init__.py +0 -0
- package/runtime/python/okstra_vendor/networkx/readwrite/json_graph/tests/test_adjacency.py +0 -78
- package/runtime/python/okstra_vendor/networkx/readwrite/json_graph/tests/test_cytoscape.py +0 -78
- package/runtime/python/okstra_vendor/networkx/readwrite/json_graph/tests/test_node_link.py +0 -109
- package/runtime/python/okstra_vendor/networkx/readwrite/json_graph/tests/test_tree.py +0 -48
- package/runtime/python/okstra_vendor/networkx/readwrite/tests/__init__.py +0 -0
- package/runtime/python/okstra_vendor/networkx/readwrite/tests/test_adjlist.py +0 -354
- package/runtime/python/okstra_vendor/networkx/readwrite/tests/test_edgelist.py +0 -318
- package/runtime/python/okstra_vendor/networkx/readwrite/tests/test_gexf.py +0 -612
- package/runtime/python/okstra_vendor/networkx/readwrite/tests/test_gml.py +0 -744
- package/runtime/python/okstra_vendor/networkx/readwrite/tests/test_graph6.py +0 -181
- package/runtime/python/okstra_vendor/networkx/readwrite/tests/test_graphml.py +0 -1531
- package/runtime/python/okstra_vendor/networkx/readwrite/tests/test_leda.py +0 -30
- package/runtime/python/okstra_vendor/networkx/readwrite/tests/test_p2g.py +0 -63
- package/runtime/python/okstra_vendor/networkx/readwrite/tests/test_pajek.py +0 -128
- package/runtime/python/okstra_vendor/networkx/readwrite/tests/test_sparse6.py +0 -166
- package/runtime/python/okstra_vendor/networkx/readwrite/tests/test_text.py +0 -1742
- package/runtime/python/okstra_vendor/networkx/tests/__init__.py +0 -0
- package/runtime/python/okstra_vendor/networkx/tests/test_all_random_functions.py +0 -248
- package/runtime/python/okstra_vendor/networkx/tests/test_convert.py +0 -321
- package/runtime/python/okstra_vendor/networkx/tests/test_convert_numpy.py +0 -531
- package/runtime/python/okstra_vendor/networkx/tests/test_convert_pandas.py +0 -349
- package/runtime/python/okstra_vendor/networkx/tests/test_convert_scipy.py +0 -281
- package/runtime/python/okstra_vendor/networkx/tests/test_exceptions.py +0 -40
- package/runtime/python/okstra_vendor/networkx/tests/test_import.py +0 -11
- package/runtime/python/okstra_vendor/networkx/tests/test_lazy_imports.py +0 -96
- package/runtime/python/okstra_vendor/networkx/tests/test_relabel.py +0 -349
- package/runtime/python/okstra_vendor/networkx/tests/test_removed_functions_exception_messages.py +0 -8
- package/runtime/python/okstra_vendor/networkx/utils/tests/__init__.py +0 -0
- package/runtime/python/okstra_vendor/networkx/utils/tests/test__init.py +0 -11
- package/runtime/python/okstra_vendor/networkx/utils/tests/test_backends.py +0 -225
- package/runtime/python/okstra_vendor/networkx/utils/tests/test_config.py +0 -263
- package/runtime/python/okstra_vendor/networkx/utils/tests/test_decorators.py +0 -510
- package/runtime/python/okstra_vendor/networkx/utils/tests/test_heaps.py +0 -131
- package/runtime/python/okstra_vendor/networkx/utils/tests/test_mapped_queue.py +0 -268
- package/runtime/python/okstra_vendor/networkx/utils/tests/test_misc.py +0 -393
- package/runtime/python/okstra_vendor/networkx/utils/tests/test_random_sequence.py +0 -53
- package/runtime/python/okstra_vendor/networkx/utils/tests/test_rcm.py +0 -63
- package/runtime/python/okstra_vendor/networkx/utils/tests/test_unionfind.py +0 -55
package/docs/kr/architecture.md
CHANGED
|
@@ -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`
|
|
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
|
|
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
package/runtime/BUILD.json
CHANGED
|
@@ -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
|
|
4
|
-
consumer state, select an available Stage Map entry, provision the
|
|
5
|
-
stage worktree, and
|
|
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
|
|
21
|
+
"""Implementation stage claim or provisioning failed."""
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
@dataclass
|
|
25
|
-
class
|
|
26
|
-
"""
|
|
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
|
|
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
|
-
) ->
|
|
61
|
-
"""
|
|
60
|
+
) -> StageRunClaim:
|
|
61
|
+
"""Claim a ready implementation stage for one run.
|
|
62
62
|
|
|
63
|
-
The returned
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
205
|
+
claim: StageRunClaim,
|
|
186
206
|
) -> None:
|
|
187
|
-
"""Publish a
|
|
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"] = [
|
|
192
|
-
csv = str(
|
|
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
|
|
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
|
|
205
|
-
ctx["EXECUTOR_WORKTREE_PATH"] =
|
|
206
|
-
ctx["EXECUTOR_WORKTREE_BRANCH"] =
|
|
207
|
-
ctx["EXECUTOR_WORKTREE_BASE_REF"] =
|
|
208
|
-
ctx["EXECUTOR_WORKTREE_STATUS"] =
|
|
209
|
-
ctx["EXECUTOR_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
|
-
|
|
961
|
-
if
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
) ->
|
|
1432
|
-
"""Compatibility wrapper for implementation stage
|
|
1431
|
+
) -> StageRunClaim:
|
|
1432
|
+
"""Compatibility wrapper for claiming an implementation stage run."""
|
|
1433
1433
|
try:
|
|
1434
|
-
return _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
|
|
1446
|
+
def _publish_stage_run_claim(
|
|
1447
1447
|
inp: PrepareInputs,
|
|
1448
1448
|
ctx: dict,
|
|
1449
1449
|
ctx_stage_map: list,
|
|
1450
|
-
|
|
1450
|
+
claim: StageRunClaim,
|
|
1451
1451
|
) -> None:
|
|
1452
|
-
"""Compatibility wrapper for publishing
|
|
1453
|
-
_implementation_stage.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
1952
|
-
# and that reserve atomic. Other task-types skip this
|
|
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
|
-
|
|
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 =
|
|
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,
|
|
1964
|
+
_clear_stale_stage_waiver(inp, project_root, stage_run_claim.stage)
|
|
1965
1965
|
else:
|
|
1966
|
-
|
|
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
|
|
1992
|
-
# STAGE worktree
|
|
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
|
-
|
|
1997
|
-
inp, ctx, ctx_stage_map,
|
|
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
|
|
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":
|
|
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
|
|
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
|