okstra 0.130.3 → 0.131.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -1
- package/docs/architecture.md +2 -2
- package/docs/cli.md +1 -1
- package/docs/for-ai/README.md +0 -2
- package/docs/for-ai/skills/okstra-run.md +13 -2
- package/docs/for-ai/skills/okstra-setup.md +4 -1
- package/docs/project-structure-overview.md +5 -4
- package/docs/task-process/implementation-planning.md +13 -0
- package/docs/task-process/implementation.md +17 -2
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/workers/report-writer-worker.md +7 -0
- package/runtime/bin/lib/okstra/usage.sh +4 -4
- package/runtime/prompts/lead/okstra-lead-contract.md +2 -2
- package/runtime/prompts/lead/plan-body-verification.md +26 -5
- package/runtime/prompts/lead/report-writer.md +6 -2
- package/runtime/prompts/profiles/_common-contract.md +2 -1
- package/runtime/prompts/profiles/_implementation-deliverable.md +6 -1
- package/runtime/prompts/profiles/_implementation-executor.md +2 -2
- package/runtime/prompts/profiles/_implementation-verifier.md +33 -10
- package/runtime/prompts/profiles/final-verification.md +21 -6
- package/runtime/prompts/profiles/implementation-planning.md +21 -4
- package/runtime/python/okstra_ctl/codex_dispatch.py +31 -0
- package/runtime/python/okstra_ctl/conformance.py +49 -11
- package/runtime/python/okstra_ctl/report_finalize.py +52 -1
- package/runtime/python/okstra_ctl/report_views.py +32 -5
- package/runtime/python/okstra_ctl/run.py +15 -3
- package/runtime/python/okstra_ctl/worker_prompt_contract.py +14 -6
- package/runtime/python/okstra_ctl/worker_prompt_policy.py +19 -1
- package/runtime/python/okstra_vendor/__init__.py +0 -44
- package/runtime/schemas/final-report-v1.0.schema.json +101 -6
- package/runtime/skills/okstra-run/SKILL.md +11 -4
- package/runtime/skills/okstra-setup/SKILL.md +3 -2
- package/runtime/skills/okstra-setup/references/project-config.md +16 -7
- package/runtime/templates/reports/final-report.template.md +18 -12
- package/runtime/templates/reports/i18n/en.json +1 -0
- package/runtime/templates/reports/i18n/ko.json +1 -0
- package/runtime/templates/reports/report.css +18 -0
- package/runtime/validators/lib/fixtures.sh +6 -0
- package/runtime/validators/validate-run.py +1722 -98
- package/src/cli-registry.mjs +0 -7
- package/src/commands/lifecycle/doctor.mjs +0 -9
- package/src/lib/skill-catalog.mjs +0 -1
- package/docs/for-ai/skills/okstra-graphify.md +0 -64
- package/runtime/python/okstra_ctl/graphify_cmd.py +0 -225
- package/runtime/python/okstra_vendor/graphify/.vendored-version +0 -1
- package/runtime/python/okstra_vendor/graphify/__init__.py +0 -28
- package/runtime/python/okstra_vendor/graphify/__main__.py +0 -1371
- package/runtime/python/okstra_vendor/graphify/analyze.py +0 -540
- package/runtime/python/okstra_vendor/graphify/benchmark.py +0 -129
- package/runtime/python/okstra_vendor/graphify/build.py +0 -107
- package/runtime/python/okstra_vendor/graphify/cache.py +0 -169
- package/runtime/python/okstra_vendor/graphify/cluster.py +0 -137
- package/runtime/python/okstra_vendor/graphify/detect.py +0 -510
- package/runtime/python/okstra_vendor/graphify/export.py +0 -1014
- package/runtime/python/okstra_vendor/graphify/extract.py +0 -3277
- package/runtime/python/okstra_vendor/graphify/hooks.py +0 -220
- package/runtime/python/okstra_vendor/graphify/ingest.py +0 -297
- package/runtime/python/okstra_vendor/graphify/manifest.py +0 -4
- package/runtime/python/okstra_vendor/graphify/report.py +0 -175
- package/runtime/python/okstra_vendor/graphify/security.py +0 -203
- package/runtime/python/okstra_vendor/graphify/serve.py +0 -373
- package/runtime/python/okstra_vendor/graphify/skill-aider.md +0 -1184
- package/runtime/python/okstra_vendor/graphify/skill-claw.md +0 -1184
- package/runtime/python/okstra_vendor/graphify/skill-codex.md +0 -1242
- package/runtime/python/okstra_vendor/graphify/skill-copilot.md +0 -1268
- package/runtime/python/okstra_vendor/graphify/skill-droid.md +0 -1239
- package/runtime/python/okstra_vendor/graphify/skill-kiro.md +0 -1183
- package/runtime/python/okstra_vendor/graphify/skill-opencode.md +0 -1238
- package/runtime/python/okstra_vendor/graphify/skill-trae.md +0 -1208
- package/runtime/python/okstra_vendor/graphify/skill-vscode.md +0 -253
- package/runtime/python/okstra_vendor/graphify/skill-windows.md +0 -1245
- package/runtime/python/okstra_vendor/graphify/skill.md +0 -1319
- package/runtime/python/okstra_vendor/graphify/transcribe.py +0 -182
- package/runtime/python/okstra_vendor/graphify/validate.py +0 -72
- package/runtime/python/okstra_vendor/graphify/watch.py +0 -188
- package/runtime/python/okstra_vendor/graphify/wiki.py +0 -214
- package/runtime/python/okstra_vendor/networkx/__init__.py +0 -62
- package/runtime/python/okstra_vendor/networkx/algorithms/__init__.py +0 -134
- package/runtime/python/okstra_vendor/networkx/algorithms/approximation/__init__.py +0 -26
- package/runtime/python/okstra_vendor/networkx/algorithms/approximation/clique.py +0 -259
- package/runtime/python/okstra_vendor/networkx/algorithms/approximation/clustering_coefficient.py +0 -71
- package/runtime/python/okstra_vendor/networkx/algorithms/approximation/connectivity.py +0 -412
- package/runtime/python/okstra_vendor/networkx/algorithms/approximation/density.py +0 -396
- package/runtime/python/okstra_vendor/networkx/algorithms/approximation/distance_measures.py +0 -150
- package/runtime/python/okstra_vendor/networkx/algorithms/approximation/dominating_set.py +0 -149
- package/runtime/python/okstra_vendor/networkx/algorithms/approximation/kcomponents.py +0 -369
- package/runtime/python/okstra_vendor/networkx/algorithms/approximation/matching.py +0 -44
- package/runtime/python/okstra_vendor/networkx/algorithms/approximation/maxcut.py +0 -143
- package/runtime/python/okstra_vendor/networkx/algorithms/approximation/ramsey.py +0 -53
- package/runtime/python/okstra_vendor/networkx/algorithms/approximation/steinertree.py +0 -265
- package/runtime/python/okstra_vendor/networkx/algorithms/approximation/traveling_salesman.py +0 -1508
- package/runtime/python/okstra_vendor/networkx/algorithms/approximation/treewidth.py +0 -255
- package/runtime/python/okstra_vendor/networkx/algorithms/approximation/vertex_cover.py +0 -83
- package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/__init__.py +0 -5
- package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/connectivity.py +0 -122
- package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/correlation.py +0 -302
- package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/mixing.py +0 -255
- package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/neighbor_degree.py +0 -160
- package/runtime/python/okstra_vendor/networkx/algorithms/assortativity/pairs.py +0 -127
- package/runtime/python/okstra_vendor/networkx/algorithms/asteroidal.py +0 -164
- package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/__init__.py +0 -88
- package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/basic.py +0 -322
- package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/centrality.py +0 -290
- package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/cluster.py +0 -289
- package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/covering.py +0 -57
- package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/edgelist.py +0 -360
- package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/extendability.py +0 -105
- package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/generators.py +0 -603
- package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/link_analysis.py +0 -316
- package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/matching.py +0 -590
- package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/matrix.py +0 -232
- package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/projection.py +0 -526
- package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/redundancy.py +0 -112
- package/runtime/python/okstra_vendor/networkx/algorithms/bipartite/spectral.py +0 -69
- package/runtime/python/okstra_vendor/networkx/algorithms/boundary.py +0 -168
- package/runtime/python/okstra_vendor/networkx/algorithms/bridges.py +0 -205
- package/runtime/python/okstra_vendor/networkx/algorithms/broadcasting.py +0 -164
- package/runtime/python/okstra_vendor/networkx/algorithms/centrality/__init__.py +0 -20
- package/runtime/python/okstra_vendor/networkx/algorithms/centrality/betweenness.py +0 -591
- package/runtime/python/okstra_vendor/networkx/algorithms/centrality/betweenness_subset.py +0 -236
- package/runtime/python/okstra_vendor/networkx/algorithms/centrality/closeness.py +0 -282
- package/runtime/python/okstra_vendor/networkx/algorithms/centrality/current_flow_betweenness.py +0 -364
- package/runtime/python/okstra_vendor/networkx/algorithms/centrality/current_flow_betweenness_subset.py +0 -227
- package/runtime/python/okstra_vendor/networkx/algorithms/centrality/current_flow_closeness.py +0 -96
- package/runtime/python/okstra_vendor/networkx/algorithms/centrality/degree_alg.py +0 -150
- package/runtime/python/okstra_vendor/networkx/algorithms/centrality/dispersion.py +0 -107
- package/runtime/python/okstra_vendor/networkx/algorithms/centrality/eigenvector.py +0 -357
- package/runtime/python/okstra_vendor/networkx/algorithms/centrality/flow_matrix.py +0 -130
- package/runtime/python/okstra_vendor/networkx/algorithms/centrality/group.py +0 -787
- package/runtime/python/okstra_vendor/networkx/algorithms/centrality/harmonic.py +0 -88
- package/runtime/python/okstra_vendor/networkx/algorithms/centrality/katz.py +0 -331
- package/runtime/python/okstra_vendor/networkx/algorithms/centrality/laplacian.py +0 -150
- package/runtime/python/okstra_vendor/networkx/algorithms/centrality/load.py +0 -200
- package/runtime/python/okstra_vendor/networkx/algorithms/centrality/percolation.py +0 -128
- package/runtime/python/okstra_vendor/networkx/algorithms/centrality/reaching.py +0 -209
- package/runtime/python/okstra_vendor/networkx/algorithms/centrality/second_order.py +0 -141
- package/runtime/python/okstra_vendor/networkx/algorithms/centrality/subgraph_alg.py +0 -361
- package/runtime/python/okstra_vendor/networkx/algorithms/centrality/trophic.py +0 -181
- package/runtime/python/okstra_vendor/networkx/algorithms/centrality/voterank_alg.py +0 -95
- package/runtime/python/okstra_vendor/networkx/algorithms/chains.py +0 -172
- package/runtime/python/okstra_vendor/networkx/algorithms/chordal.py +0 -443
- package/runtime/python/okstra_vendor/networkx/algorithms/clique.py +0 -818
- package/runtime/python/okstra_vendor/networkx/algorithms/cluster.py +0 -732
- package/runtime/python/okstra_vendor/networkx/algorithms/coloring/__init__.py +0 -4
- package/runtime/python/okstra_vendor/networkx/algorithms/coloring/equitable_coloring.py +0 -505
- package/runtime/python/okstra_vendor/networkx/algorithms/coloring/greedy_coloring.py +0 -565
- package/runtime/python/okstra_vendor/networkx/algorithms/communicability_alg.py +0 -163
- package/runtime/python/okstra_vendor/networkx/algorithms/community/__init__.py +0 -28
- package/runtime/python/okstra_vendor/networkx/algorithms/community/asyn_fluid.py +0 -153
- package/runtime/python/okstra_vendor/networkx/algorithms/community/bipartitions.py +0 -354
- package/runtime/python/okstra_vendor/networkx/algorithms/community/centrality.py +0 -171
- package/runtime/python/okstra_vendor/networkx/algorithms/community/community_utils.py +0 -30
- package/runtime/python/okstra_vendor/networkx/algorithms/community/divisive.py +0 -216
- package/runtime/python/okstra_vendor/networkx/algorithms/community/kclique.py +0 -79
- package/runtime/python/okstra_vendor/networkx/algorithms/community/label_propagation.py +0 -338
- package/runtime/python/okstra_vendor/networkx/algorithms/community/leiden.py +0 -162
- package/runtime/python/okstra_vendor/networkx/algorithms/community/local.py +0 -220
- package/runtime/python/okstra_vendor/networkx/algorithms/community/louvain.py +0 -384
- package/runtime/python/okstra_vendor/networkx/algorithms/community/lukes.py +0 -227
- package/runtime/python/okstra_vendor/networkx/algorithms/community/modularity_max.py +0 -452
- package/runtime/python/okstra_vendor/networkx/algorithms/community/quality.py +0 -347
- package/runtime/python/okstra_vendor/networkx/algorithms/components/__init__.py +0 -6
- package/runtime/python/okstra_vendor/networkx/algorithms/components/attracting.py +0 -115
- package/runtime/python/okstra_vendor/networkx/algorithms/components/biconnected.py +0 -394
- package/runtime/python/okstra_vendor/networkx/algorithms/components/connected.py +0 -282
- package/runtime/python/okstra_vendor/networkx/algorithms/components/semiconnected.py +0 -71
- package/runtime/python/okstra_vendor/networkx/algorithms/components/strongly_connected.py +0 -359
- package/runtime/python/okstra_vendor/networkx/algorithms/components/weakly_connected.py +0 -196
- package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/__init__.py +0 -11
- package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/connectivity.py +0 -811
- package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/cuts.py +0 -616
- package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/disjoint_paths.py +0 -408
- package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/edge_augmentation.py +0 -1270
- package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/edge_kcomponents.py +0 -592
- package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/kcomponents.py +0 -220
- package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/kcutsets.py +0 -235
- package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/stoerwagner.py +0 -152
- package/runtime/python/okstra_vendor/networkx/algorithms/connectivity/utils.py +0 -88
- package/runtime/python/okstra_vendor/networkx/algorithms/core.py +0 -588
- package/runtime/python/okstra_vendor/networkx/algorithms/covering.py +0 -142
- package/runtime/python/okstra_vendor/networkx/algorithms/cuts.py +0 -416
- package/runtime/python/okstra_vendor/networkx/algorithms/cycles.py +0 -1234
- package/runtime/python/okstra_vendor/networkx/algorithms/d_separation.py +0 -677
- package/runtime/python/okstra_vendor/networkx/algorithms/dag.py +0 -1392
- package/runtime/python/okstra_vendor/networkx/algorithms/distance_measures.py +0 -1095
- package/runtime/python/okstra_vendor/networkx/algorithms/distance_regular.py +0 -272
- package/runtime/python/okstra_vendor/networkx/algorithms/dominance.py +0 -142
- package/runtime/python/okstra_vendor/networkx/algorithms/dominating.py +0 -268
- package/runtime/python/okstra_vendor/networkx/algorithms/efficiency_measures.py +0 -167
- package/runtime/python/okstra_vendor/networkx/algorithms/euler.py +0 -470
- package/runtime/python/okstra_vendor/networkx/algorithms/flow/__init__.py +0 -11
- package/runtime/python/okstra_vendor/networkx/algorithms/flow/boykovkolmogorov.py +0 -370
- package/runtime/python/okstra_vendor/networkx/algorithms/flow/capacityscaling.py +0 -407
- package/runtime/python/okstra_vendor/networkx/algorithms/flow/dinitz_alg.py +0 -238
- package/runtime/python/okstra_vendor/networkx/algorithms/flow/edmondskarp.py +0 -241
- package/runtime/python/okstra_vendor/networkx/algorithms/flow/gomory_hu.py +0 -178
- package/runtime/python/okstra_vendor/networkx/algorithms/flow/maxflow.py +0 -611
- package/runtime/python/okstra_vendor/networkx/algorithms/flow/mincost.py +0 -356
- package/runtime/python/okstra_vendor/networkx/algorithms/flow/networksimplex.py +0 -662
- package/runtime/python/okstra_vendor/networkx/algorithms/flow/preflowpush.py +0 -425
- package/runtime/python/okstra_vendor/networkx/algorithms/flow/shortestaugmentingpath.py +0 -300
- package/runtime/python/okstra_vendor/networkx/algorithms/flow/utils.py +0 -194
- package/runtime/python/okstra_vendor/networkx/algorithms/graph_hashing.py +0 -435
- package/runtime/python/okstra_vendor/networkx/algorithms/graphical.py +0 -483
- package/runtime/python/okstra_vendor/networkx/algorithms/hierarchy.py +0 -57
- package/runtime/python/okstra_vendor/networkx/algorithms/hybrid.py +0 -196
- package/runtime/python/okstra_vendor/networkx/algorithms/isolate.py +0 -107
- package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/__init__.py +0 -7
- package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/ismags.py +0 -1306
- package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/isomorph.py +0 -336
- package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/isomorphvf2.py +0 -1262
- package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/matchhelpers.py +0 -352
- package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/temporalisomorphvf2.py +0 -308
- package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/tree_isomorphism.py +0 -264
- package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/vf2pp.py +0 -1102
- package/runtime/python/okstra_vendor/networkx/algorithms/isomorphism/vf2userfunc.py +0 -192
- package/runtime/python/okstra_vendor/networkx/algorithms/link_analysis/__init__.py +0 -2
- package/runtime/python/okstra_vendor/networkx/algorithms/link_analysis/hits_alg.py +0 -337
- package/runtime/python/okstra_vendor/networkx/algorithms/link_analysis/pagerank_alg.py +0 -498
- package/runtime/python/okstra_vendor/networkx/algorithms/link_prediction.py +0 -687
- package/runtime/python/okstra_vendor/networkx/algorithms/lowest_common_ancestors.py +0 -280
- package/runtime/python/okstra_vendor/networkx/algorithms/matching.py +0 -1148
- package/runtime/python/okstra_vendor/networkx/algorithms/minors/__init__.py +0 -27
- package/runtime/python/okstra_vendor/networkx/algorithms/minors/contraction.py +0 -738
- package/runtime/python/okstra_vendor/networkx/algorithms/mis.py +0 -78
- package/runtime/python/okstra_vendor/networkx/algorithms/moral.py +0 -59
- package/runtime/python/okstra_vendor/networkx/algorithms/node_classification.py +0 -219
- package/runtime/python/okstra_vendor/networkx/algorithms/non_randomness.py +0 -155
- package/runtime/python/okstra_vendor/networkx/algorithms/operators/__init__.py +0 -4
- package/runtime/python/okstra_vendor/networkx/algorithms/operators/all.py +0 -324
- package/runtime/python/okstra_vendor/networkx/algorithms/operators/binary.py +0 -468
- package/runtime/python/okstra_vendor/networkx/algorithms/operators/product.py +0 -633
- package/runtime/python/okstra_vendor/networkx/algorithms/operators/unary.py +0 -77
- package/runtime/python/okstra_vendor/networkx/algorithms/perfect_graph.py +0 -73
- package/runtime/python/okstra_vendor/networkx/algorithms/planar_drawing.py +0 -464
- package/runtime/python/okstra_vendor/networkx/algorithms/planarity.py +0 -1463
- package/runtime/python/okstra_vendor/networkx/algorithms/polynomials.py +0 -306
- package/runtime/python/okstra_vendor/networkx/algorithms/reciprocity.py +0 -98
- package/runtime/python/okstra_vendor/networkx/algorithms/regular.py +0 -167
- package/runtime/python/okstra_vendor/networkx/algorithms/richclub.py +0 -138
- package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/__init__.py +0 -5
- package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/astar.py +0 -239
- package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/dense.py +0 -264
- package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/generic.py +0 -716
- package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/unweighted.py +0 -625
- package/runtime/python/okstra_vendor/networkx/algorithms/shortest_paths/weighted.py +0 -2542
- package/runtime/python/okstra_vendor/networkx/algorithms/similarity.py +0 -2107
- package/runtime/python/okstra_vendor/networkx/algorithms/simple_paths.py +0 -966
- package/runtime/python/okstra_vendor/networkx/algorithms/smallworld.py +0 -404
- package/runtime/python/okstra_vendor/networkx/algorithms/smetric.py +0 -30
- package/runtime/python/okstra_vendor/networkx/algorithms/sparsifiers.py +0 -296
- package/runtime/python/okstra_vendor/networkx/algorithms/structuralholes.py +0 -374
- package/runtime/python/okstra_vendor/networkx/algorithms/summarization.py +0 -564
- package/runtime/python/okstra_vendor/networkx/algorithms/swap.py +0 -406
- package/runtime/python/okstra_vendor/networkx/algorithms/threshold.py +0 -981
- package/runtime/python/okstra_vendor/networkx/algorithms/time_dependent.py +0 -142
- package/runtime/python/okstra_vendor/networkx/algorithms/tournament.py +0 -406
- package/runtime/python/okstra_vendor/networkx/algorithms/traversal/__init__.py +0 -5
- package/runtime/python/okstra_vendor/networkx/algorithms/traversal/beamsearch.py +0 -90
- package/runtime/python/okstra_vendor/networkx/algorithms/traversal/breadth_first_search.py +0 -576
- package/runtime/python/okstra_vendor/networkx/algorithms/traversal/depth_first_search.py +0 -529
- package/runtime/python/okstra_vendor/networkx/algorithms/traversal/edgebfs.py +0 -185
- package/runtime/python/okstra_vendor/networkx/algorithms/traversal/edgedfs.py +0 -182
- package/runtime/python/okstra_vendor/networkx/algorithms/tree/__init__.py +0 -7
- package/runtime/python/okstra_vendor/networkx/algorithms/tree/branchings.py +0 -1042
- package/runtime/python/okstra_vendor/networkx/algorithms/tree/coding.py +0 -413
- package/runtime/python/okstra_vendor/networkx/algorithms/tree/decomposition.py +0 -88
- package/runtime/python/okstra_vendor/networkx/algorithms/tree/distance_measures.py +0 -219
- package/runtime/python/okstra_vendor/networkx/algorithms/tree/mst.py +0 -1281
- package/runtime/python/okstra_vendor/networkx/algorithms/tree/operations.py +0 -106
- package/runtime/python/okstra_vendor/networkx/algorithms/tree/recognition.py +0 -273
- package/runtime/python/okstra_vendor/networkx/algorithms/triads.py +0 -500
- package/runtime/python/okstra_vendor/networkx/algorithms/vitality.py +0 -76
- package/runtime/python/okstra_vendor/networkx/algorithms/voronoi.py +0 -86
- package/runtime/python/okstra_vendor/networkx/algorithms/walks.py +0 -77
- package/runtime/python/okstra_vendor/networkx/algorithms/wiener.py +0 -278
- package/runtime/python/okstra_vendor/networkx/classes/__init__.py +0 -13
- package/runtime/python/okstra_vendor/networkx/classes/coreviews.py +0 -435
- package/runtime/python/okstra_vendor/networkx/classes/digraph.py +0 -1363
- package/runtime/python/okstra_vendor/networkx/classes/filters.py +0 -95
- package/runtime/python/okstra_vendor/networkx/classes/function.py +0 -1549
- package/runtime/python/okstra_vendor/networkx/classes/graph.py +0 -2082
- package/runtime/python/okstra_vendor/networkx/classes/graphviews.py +0 -269
- package/runtime/python/okstra_vendor/networkx/classes/multidigraph.py +0 -977
- package/runtime/python/okstra_vendor/networkx/classes/multigraph.py +0 -1294
- package/runtime/python/okstra_vendor/networkx/classes/reportviews.py +0 -1447
- package/runtime/python/okstra_vendor/networkx/convert.py +0 -502
- package/runtime/python/okstra_vendor/networkx/convert_matrix.py +0 -1314
- package/runtime/python/okstra_vendor/networkx/drawing/__init__.py +0 -7
- package/runtime/python/okstra_vendor/networkx/drawing/layout.py +0 -2036
- package/runtime/python/okstra_vendor/networkx/drawing/nx_agraph.py +0 -470
- package/runtime/python/okstra_vendor/networkx/drawing/nx_latex.py +0 -570
- package/runtime/python/okstra_vendor/networkx/drawing/nx_pydot.py +0 -361
- package/runtime/python/okstra_vendor/networkx/drawing/nx_pylab.py +0 -2978
- package/runtime/python/okstra_vendor/networkx/exception.py +0 -131
- package/runtime/python/okstra_vendor/networkx/generators/__init__.py +0 -34
- package/runtime/python/okstra_vendor/networkx/generators/atlas.dat.gz +0 -0
- package/runtime/python/okstra_vendor/networkx/generators/atlas.py +0 -227
- package/runtime/python/okstra_vendor/networkx/generators/classic.py +0 -1091
- package/runtime/python/okstra_vendor/networkx/generators/cographs.py +0 -68
- package/runtime/python/okstra_vendor/networkx/generators/community.py +0 -1070
- package/runtime/python/okstra_vendor/networkx/generators/degree_seq.py +0 -886
- package/runtime/python/okstra_vendor/networkx/generators/directed.py +0 -572
- package/runtime/python/okstra_vendor/networkx/generators/duplication.py +0 -174
- package/runtime/python/okstra_vendor/networkx/generators/ego.py +0 -66
- package/runtime/python/okstra_vendor/networkx/generators/expanders.py +0 -499
- package/runtime/python/okstra_vendor/networkx/generators/geometric.py +0 -1037
- package/runtime/python/okstra_vendor/networkx/generators/harary_graph.py +0 -163
- package/runtime/python/okstra_vendor/networkx/generators/internet_as_graphs.py +0 -443
- package/runtime/python/okstra_vendor/networkx/generators/intersection.py +0 -125
- package/runtime/python/okstra_vendor/networkx/generators/interval_graph.py +0 -70
- package/runtime/python/okstra_vendor/networkx/generators/joint_degree_seq.py +0 -664
- package/runtime/python/okstra_vendor/networkx/generators/lattice.py +0 -405
- package/runtime/python/okstra_vendor/networkx/generators/line.py +0 -501
- package/runtime/python/okstra_vendor/networkx/generators/mycielski.py +0 -110
- package/runtime/python/okstra_vendor/networkx/generators/nonisomorphic_trees.py +0 -259
- package/runtime/python/okstra_vendor/networkx/generators/random_clustered.py +0 -117
- package/runtime/python/okstra_vendor/networkx/generators/random_graphs.py +0 -1416
- package/runtime/python/okstra_vendor/networkx/generators/small.py +0 -1070
- package/runtime/python/okstra_vendor/networkx/generators/social.py +0 -554
- package/runtime/python/okstra_vendor/networkx/generators/spectral_graph_forge.py +0 -120
- package/runtime/python/okstra_vendor/networkx/generators/stochastic.py +0 -54
- package/runtime/python/okstra_vendor/networkx/generators/sudoku.py +0 -131
- package/runtime/python/okstra_vendor/networkx/generators/time_series.py +0 -74
- package/runtime/python/okstra_vendor/networkx/generators/trees.py +0 -1070
- package/runtime/python/okstra_vendor/networkx/generators/triads.py +0 -94
- package/runtime/python/okstra_vendor/networkx/lazy_imports.py +0 -188
- package/runtime/python/okstra_vendor/networkx/linalg/__init__.py +0 -13
- package/runtime/python/okstra_vendor/networkx/linalg/algebraicconnectivity.py +0 -650
- package/runtime/python/okstra_vendor/networkx/linalg/attrmatrix.py +0 -466
- package/runtime/python/okstra_vendor/networkx/linalg/bethehessianmatrix.py +0 -77
- package/runtime/python/okstra_vendor/networkx/linalg/graphmatrix.py +0 -168
- package/runtime/python/okstra_vendor/networkx/linalg/laplacianmatrix.py +0 -512
- package/runtime/python/okstra_vendor/networkx/linalg/modularitymatrix.py +0 -166
- package/runtime/python/okstra_vendor/networkx/linalg/spectrum.py +0 -186
- package/runtime/python/okstra_vendor/networkx/readwrite/__init__.py +0 -17
- package/runtime/python/okstra_vendor/networkx/readwrite/adjlist.py +0 -330
- package/runtime/python/okstra_vendor/networkx/readwrite/edgelist.py +0 -489
- package/runtime/python/okstra_vendor/networkx/readwrite/gexf.py +0 -1084
- package/runtime/python/okstra_vendor/networkx/readwrite/gml.py +0 -879
- package/runtime/python/okstra_vendor/networkx/readwrite/graph6.py +0 -427
- package/runtime/python/okstra_vendor/networkx/readwrite/graphml.py +0 -1053
- package/runtime/python/okstra_vendor/networkx/readwrite/json_graph/__init__.py +0 -19
- package/runtime/python/okstra_vendor/networkx/readwrite/json_graph/adjacency.py +0 -156
- package/runtime/python/okstra_vendor/networkx/readwrite/json_graph/cytoscape.py +0 -190
- package/runtime/python/okstra_vendor/networkx/readwrite/json_graph/node_link.py +0 -261
- package/runtime/python/okstra_vendor/networkx/readwrite/json_graph/tree.py +0 -137
- package/runtime/python/okstra_vendor/networkx/readwrite/leda.py +0 -108
- package/runtime/python/okstra_vendor/networkx/readwrite/multiline_adjlist.py +0 -393
- package/runtime/python/okstra_vendor/networkx/readwrite/p2g.py +0 -113
- package/runtime/python/okstra_vendor/networkx/readwrite/pajek.py +0 -286
- package/runtime/python/okstra_vendor/networkx/readwrite/sparse6.py +0 -379
- package/runtime/python/okstra_vendor/networkx/readwrite/text.py +0 -851
- package/runtime/python/okstra_vendor/networkx/relabel.py +0 -285
- package/runtime/python/okstra_vendor/networkx/utils/__init__.py +0 -8
- package/runtime/python/okstra_vendor/networkx/utils/backends.py +0 -2171
- package/runtime/python/okstra_vendor/networkx/utils/configs.py +0 -396
- package/runtime/python/okstra_vendor/networkx/utils/decorators.py +0 -1233
- package/runtime/python/okstra_vendor/networkx/utils/heaps.py +0 -338
- package/runtime/python/okstra_vendor/networkx/utils/mapped_queue.py +0 -297
- package/runtime/python/okstra_vendor/networkx/utils/misc.py +0 -703
- package/runtime/python/okstra_vendor/networkx/utils/random_sequence.py +0 -198
- package/runtime/python/okstra_vendor/networkx/utils/rcm.py +0 -159
- package/runtime/python/okstra_vendor/networkx/utils/union_find.py +0 -106
- package/runtime/skills/okstra-graphify/SKILL.md +0 -169
- package/src/commands/graphify.mjs +0 -32
|
@@ -42,14 +42,35 @@ Verifier obtains the QA command set from exactly two declared sources, in order
|
|
|
42
42
|
|
|
43
43
|
Tier 1 commands run verbatim first. Then every Tier 2 entry runs once. Then the Tier 3 stage conformance script (below) runs once. Each command runs in the worktree cwd, and is recorded in the worker result with its exact command line, exit code, and the tail of stdout/stderr. Substituting or paraphrasing a Tier 1 command is forbidden (see Verifier-specific forbidden actions below).
|
|
44
44
|
|
|
45
|
-
### Tier 3 — stage conformance scripts
|
|
46
|
-
|
|
47
|
-
Tier 3
|
|
45
|
+
### Tier 3 — stage conformance scripts
|
|
46
|
+
|
|
47
|
+
Tier 3 still attempts the declared `runCommand` against the permitted test
|
|
48
|
+
environment. Classify the manifest entry by `requires`: any `db`, `http`, or
|
|
49
|
+
`external` capability makes the entry external-advisory; an empty list or
|
|
50
|
+
`io`-only entry stays blocking.
|
|
51
|
+
|
|
52
|
+
**External QA outcome policy.** Record PASS normally. For FAIL, MISSING, no
|
|
53
|
+
result, startup failure, credential/network/service absence, write the honest
|
|
54
|
+
sidecar and command evidence with status `ADVISORY`. That result alone
|
|
55
|
+
MUST NOT change the overall verifier verdict from PASS to CONCERNS/FAIL, trigger a fix
|
|
56
|
+
cycle, or block the stage. Return the exact command, prerequisites, observed
|
|
57
|
+
result, and expected result to the report writer as a user-owned follow-up.
|
|
58
|
+
Enforcement: `scripts/okstra_ctl/conformance.py` (`decide_conformance_gate`)
|
|
59
|
+
performs the core advisory classification/reduction;
|
|
60
|
+
`validators/validate-run.py` (`_validate_conformance`) routes that status to
|
|
61
|
+
warnings instead of failures; and
|
|
62
|
+
`tests/contract/test_okstra_ctl_conformance.py` (`test_external_non_pass_is_advisory`),
|
|
63
|
+
`tests/contract/test_validate_run_conformance.py` (`test_external_non_pass_returns_warning_not_failure`),
|
|
64
|
+
and `tests/contract/test_final_report_contracts.py` (`test_external_qa_advisory_renders_without_downgrading_accepted_verdict`)
|
|
65
|
+
lock the core, gate, and accepted-report behavior respectively.
|
|
66
|
+
|
|
67
|
+
An `io`-only non-PASS remains BLOCKING. Manifest/schema/source-mutation defects
|
|
68
|
+
also remain contract violations.
|
|
48
69
|
|
|
49
70
|
- **Source.** The conformance manifest is `<task_root>/qa/conformance-manifest.json` (the directory is the `TASK_QA_PATH` token). This run's stage conformance entry is the manifest `entries[]` item whose `stageKey` equals this run's stageKey — `<task-id>-stage-<N>`, where `<N>` is the injected Stage number. Find that one entry; ignore the others (other stages are run by their own implementation runs or by final-verification).
|
|
50
|
-
- **Exemption / waiver → do NOT run.** If the entry carries an `exemption` (or a user `waiver`), the verifier does NOT execute the script. It records the fact and the reason (`exemption.reason` / `waiver.reason` + `waiver.acknowledgedBy`) in the Read-only command log AND writes the result sidecar reflecting the skip. An `exemption` passes
|
|
71
|
+
- **Exemption / waiver → do NOT run.** If the entry carries an `exemption` (or a user `waiver`), the verifier does NOT execute the script. It records the fact and the reason (`exemption.reason` / `waiver.reason` + `waiver.acknowledgedBy`) in the Read-only command log AND writes the result sidecar reflecting the skip. An `exemption` passes outright. An external-advisory waiver is reported as `ADVISORY` with `conditional=false`; only an `io`-only blocking waiver is conditional. An empty `requires` list cannot be waived; it is declaration/contract trouble and remains BLOCKING. No script runs in either permitted waiver case.
|
|
51
72
|
- **Otherwise run `runCommand` in the worktree cwd.** Execute the entry's `runCommand` verbatim from the worktree cwd. Inject env from `<PROJECT_ROOT>/.okstra/project.json`'s `qaEnv` (replica DB DSN / app base URL / env file — declared in Phase 4e). This is a **replica / test environment only** path — never run it against shared / staging / prod, identical to the DB real-execution gate principle above.
|
|
52
|
-
- **Interpret the standard interface.** Parse the process exit code together with stdout: the `QA-RESULT: PASS|FAIL` marker line (if several appear, the last one wins) and the per-requirement `REQ <id>: PASS|FAIL: <reason>` lines. If no `QA-RESULT` marker is emitted, the overall result is `MISSING
|
|
73
|
+
- **Interpret the standard interface.** Parse the process exit code together with stdout: the `QA-RESULT: PASS|FAIL` marker line (if several appear, the last one wins) and the per-requirement `REQ <id>: PASS|FAIL: <reason>` lines. If no `QA-RESULT` marker is emitted, the overall result is `MISSING`; classify it according to the entry's blocking or external-advisory capability policy above.
|
|
53
74
|
- **Write the result sidecar (BLOCKING deliverable).** Write `<task_root>/qa/result-<stageKey>.json` as:
|
|
54
75
|
```json
|
|
55
76
|
{
|
|
@@ -59,13 +80,13 @@ Tier 3 proves the stage actually *meets the upper-level requirement* it was scop
|
|
|
59
80
|
"requirements": { "<id>": { "status": "PASS", "reason": "<from REQ line>" } }
|
|
60
81
|
}
|
|
61
82
|
```
|
|
62
|
-
`overall` is exactly one of `PASS` / `FAIL` / `MISSING`.
|
|
63
|
-
- **Read-only command log.** Record the `runCommand` exact line + its exit code in the Read-only command log. Unlike Tiers 1·2, a conformance script MAY mutate the **replica datastore** (exercising integrated state is its whole purpose) — but only the `qaEnv` replica target, never a shared/staging/prod store. The `runCommand` itself is still subject to the same source/lockfile mutation deny-list as Tier 2 (`--fix`, `npm install` without `ci`, etc.); a denied token aborts with `contract-violated`.
|
|
83
|
+
`overall` is exactly one of `PASS` / `FAIL` / `MISSING`. Writing the honest sidecar is mandatory whenever the script runs and on the exemption/waiver skip path. A missing `io`-only sidecar blocks; a missing external-advisory sidecar is reported as `ADVISORY` rather than accepted as hidden evidence.
|
|
84
|
+
- **Read-only command log.** Record the `runCommand` exact line + its exit code in the Read-only command log. Tier 3 external non-PASS evidence MUST remain visible with status `ADVISORY`. Unlike Tiers 1·2, a conformance script MAY mutate the **replica datastore** (exercising integrated state is its whole purpose) — but only the `qaEnv` replica target, never a shared/staging/prod store. The `runCommand` itself is still subject to the same source/lockfile mutation deny-list as Tier 2 (`--fix`, `npm install` without `ci`, etc.); a denied token aborts with `contract-violated`.
|
|
64
85
|
- **No manifest / no entry for this stage.** If the manifest file is absent, or it has no entry whose `stageKey` matches this run's stageKey, the verifier records `conformance: no manifest entry for <stageKey>` and proceeds (forcing the *declaration* of conformance entries is the job of planning Step 11 + the `validate-run.py` diff-surface cross-check, not the verifier).
|
|
65
86
|
|
|
66
87
|
### Missing-tier handling
|
|
67
88
|
|
|
68
|
-
If a tier is empty or absent, verifier records the single line `qa-command not configured: <category>` per missing category (`lint` / `format` / `typecheck` / `test`; and `db-test` **only when the diff touches DB/IO/SQL**, where a missing `db-test` is escalated to a blocking finding per the DB real-execution gate below) in the worker result and proceeds — silent omission is a contract violation. Verifier MUST NOT auto-detect or invent a command in this case; the user/operator must declare it in `project.json.qaCommands` or in the plan.
|
|
89
|
+
If a tier is empty or absent, verifier records the single line `qa-command not configured: <category>` per missing category (`lint` / `format` / `typecheck` / `test`; and `db-test` **only when the diff touches DB/IO/SQL**, where a missing `db-test` is escalated to a blocking finding per the DB real-execution gate below) in the worker result and proceeds — silent omission is a contract violation. **Enforced:** `validators/validate-run.py` `_validate_missing_qa_categories_recorded` for the four unconditional categories; `db-test` is left to the DB gate below because its requirement depends on whether the diff touches DB/IO/SQL. Without the note, "the category passed" and "the category never ran" read identically in the report. Verifier MUST NOT auto-detect or invent a command in this case; the user/operator must declare it in `project.json.qaCommands` or in the plan.
|
|
69
90
|
|
|
70
91
|
### `cmd` field deny-list (Tier 2 validation)
|
|
71
92
|
|
|
@@ -73,7 +94,7 @@ The runtime AND the verifier MUST reject any `cmd` containing tokens that imply
|
|
|
73
94
|
|
|
74
95
|
### Discrepancy rule
|
|
75
96
|
|
|
76
|
-
|
|
97
|
+
Tier 3 external-advisory discrepancies are excluded from this promotion: preserve the executor/verifier divergence in the advisory evidence and user-owned follow-up without changing the verdict. For Tier 1, Tier 2, and blocking `io`-only Tier 3, if the verifier's re-run result differs from what the executor reported (a passing test fails on re-run, a clean lint surfaces warnings, an exit code mismatches), the verifier MUST issue verdict `FAIL` with the divergence cited. `Claude lead` MUST NOT silently prefer the executor's evidence over a verifier's reproduced result during synthesis; if it overrides, it MUST cite a concrete reproduction-time reason (flaky-test commit-cited, environment delta documented) — handwaving is not allowed.
|
|
77
98
|
|
|
78
99
|
### Read-only command log (per verifier)
|
|
79
100
|
|
|
@@ -132,11 +153,13 @@ carried findings is a contract violation the lead records via
|
|
|
132
153
|
|
|
133
154
|
A mocked unit test cannot observe the SQL a query builder actually emits — `count({ col: 'FontFamily.fontFamily' })` passes a mocked suite yet throws `Unknown column` on a real database. For this class of change a green mock-only suite is therefore NOT evidence; only a run against a real (or faithful-replica) datastore is. This gate is the verifier's enforcement of that rule.
|
|
134
155
|
|
|
156
|
+
**External Tier 3 de-duplication exception.** A DB/IO/SQL surface covered by an in-scope Tier 3 entry whose `requires` include `db`, `http`, or `external` is governed by the External QA outcome policy. Its non-PASS or unavailable result MUST NOT generate a second legacy db-test-not-configured or mock-only blocker solely for that same Tier 3 non-PASS or unavailable result. Tier 1 or Tier 2 failures remain blocking, and DB surfaces without declared external Tier 3 coverage remain blocking.
|
|
157
|
+
|
|
135
158
|
- **Trigger.** Fires when `git diff <base>...HEAD` touches DB/IO/SQL: ORM / query-builder code (sequelize / typeorm / prisma / knex / raw SQL), `*.repository.*`, model/entity files, `migrations/**`, `*.sql`, or any changed query string.
|
|
136
159
|
- **Requirement when fired.** The verifier MUST reproduce a real-DB execution: run the `db-test` tier (Tier 1 = plan `validation` db step; else Tier 2 = `project.json.qaCommands.db-test`) against a **local / replica** datastore (same engine + schema — never shared / staging / prod, consistent with the verifier forbidden-actions list) and record its exact command + exit code. A mock, an in-memory shim that does not parse real SQL, or static reasoning does NOT satisfy this.
|
|
137
160
|
- **No `db-test` command available → blocking, not a passive skip.** If neither tier declares a `db-test` command, the verifier records the blocking finding `db-test not configured — DB change unverified (mock-only)` and sets the verdict to `FAIL`; it MUST NOT emit only the passive `qa-command not configured` note and pass. Recommended fix: declare a `db-test` command in `project.json.qaCommands` or the plan's validation set.
|
|
138
161
|
- **Mock-only evidence → unverified.** If the diff's only DB coverage is mocked, the verifier labels the DB portion `static-analysis only …, unverified (not executed)` (never `verified`), records it as a blocking finding, and sets `FAIL`. Never downplay the real run as "too heavy / static proof suffices".
|
|
139
|
-
- **Surface it at every layer.** The finding is copied verbatim into the verifier result and MUST survive into the final report's `## 6.` and Verdict Card, so the user sees the DB-unverified state continuously — it is the load-bearing reason a downstream `final-verification` cannot reach `accepted` and `release-handoff` cannot push.
|
|
162
|
+
- **Surface it at every layer.** The finding is copied verbatim into the verifier result and MUST survive into the final report's `## 6.` and Verdict Card, so the user sees the DB-unverified state continuously — it is the load-bearing reason a downstream `final-verification` cannot reach `accepted` and `release-handoff` cannot push. **Enforced:** `validators/validate-run.py` `_validate_verifier_fail_blocks_verdict` fails a report whose `verdictCard.verdictToken` is `accepted` / `conditional-accept` while any `implementation.verifierResults[]` row records `verdict: FAIL` — a rejection dropped during synthesis is exactly how rejected work reached `release-handoff`.
|
|
140
163
|
|
|
141
164
|
## All-verifier-failure policy
|
|
142
165
|
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
- test & validation suite pass status — independently re-run the read-only two-tier command set (Tier 1 = brief/approved-plan `validation`, Tier 2 = `project.json` `qaCommands`) and confirm each passes on the verified head, citing exact command + exit code
|
|
16
16
|
- test correctness — delivered tests actually assert the intended behaviour: no gutted/weakened assertions, no tautological or always-passing tests, no tests exercising only mocks; new behaviour has matching coverage
|
|
17
17
|
- DB / IO / SQL real-execution evidence — trigger: the diff touches DB/IO/SQL (ORM / query-builder, `*.repository.*`, model / `migrations/**` / `*.sql`, or changed query strings). Then Validation Evidence MUST cite a real (or faithful-replica) DB execution — the `db-test` command + exit code — not a mock-only suite. Rationale: a mock-only suite cannot observe the SQL actually emitted (observed failure class: `prompts/profiles/_implementation-verifier.md` §"DB / IO / SQL change — real-execution gate"). A DB-touching change whose only evidence is mocked, or for which no `db-test` ran, is an **Acceptance Blocker** (`major`+; per the Verdict vocabulary below, any blocker moves the verdict off `accepted`). This gate stops an unverified DB change from reaching `release-handoff` and being pushed.
|
|
18
|
+
- **External Tier 3 de-duplication exception.** A DB/IO/SQL surface covered by an in-scope Tier 3 entry whose `requires` include `db`, `http`, or `external` is governed by the External QA outcome policy. Its non-PASS or unavailable result MUST NOT generate a second legacy db-test-not-configured or mock-only blocker solely for that same Tier 3 non-PASS or unavailable result. Tier 1 or Tier 2 failures remain blocking, and DB surfaces without declared external Tier 3 coverage remain blocking.
|
|
18
19
|
- no new defects introduced — the diff does not break previously-working behaviour and adds no new bug (logic/off-by-one, null/empty handling, resource leaks, broken error paths)
|
|
19
20
|
- scope conformance — the delivered diff stays within the approved plan's scope; flag out-of-scope edits, unrelated file changes, leftover debug/commented-out code, and unintended deletions
|
|
20
21
|
- project review-rule packs (when present) — search `<PROJECT_ROOT>/skills/*review*`, `<PROJECT_ROOT>/.claude/skills/*review*`, and up to two parent directories' `skills/*review*/SKILL.md`; read the matching `SKILL.md` + referenced `references/*.md` and apply their rules as an acceptance overlay (record `project-review-rules: <paths read>` or `project-review-rules: none found`). This is a static review pass, not a PR-comment workflow — do NOT dispatch reviewer subagents. Because this phase verifies the **whole-task merged diff**, it is the gate that catches **cross-stage findings a per-stage `implementation` verifier structurally cannot see** (each implementation run reviews only its own stage diff): most importantly two cross-stage conditions: (a) the same helper stack / transform / domain rule duplicated across stages or services — byte-identical duplication is always an Acceptance Blocker, and semantically-equivalent transforms across services are blockers unless the approved plan explicitly justified keeping them separate; (b) an API newly orphaned because its only caller was removed in a different stage. A confirmed cross-stage duplication of this kind is an Acceptance Blocker (`major`+) that cites every `path:line` location and names the shared-module location to converge on. (Single-stage scope sees only one stage, so it cannot raise cross-stage findings — note that limitation rather than implying coverage.)
|
|
@@ -33,27 +34,41 @@
|
|
|
33
34
|
- **Target confirmation:** verify the inline worktree, scope, base/head refs, target path, and digest against `verification-target.md` before analysis. Use the sidecar's stage/report mapping and complete diff stat; a missing sidecar, digest mismatch, dirty worktree outside `.okstra/`, or wrong head is a `tool-failure`, never a silent target reselection.
|
|
34
35
|
- **Evidence:** attach file:line, exact command + exit code, log excerpt, or MCP SELECT evidence to every finding. Mark a requirement as covered only when the cited artifact demonstrates it.
|
|
35
36
|
- **Tier 1 and Tier 2 read-only validation:** Tier 1 is the originating brief/approved plan `validation` set; Tier 2 is `<PROJECT_ROOT>/.okstra/project.json` `qaCommands`. Do not auto-detect commands from package manifests. A missing tier is `qa-command not configured: <category>`. Before execution, reject commands containing source/lockfile mutation tokens such as `--fix`, `--write`, ` -w`, ` -u`, `--snapshot-update`, `INSTA_UPDATE=<not-no>`, `cargo update`, or `npm install` without `ci`; record the exact denied token.
|
|
36
|
-
- **
|
|
37
|
+
- **External QA outcome policy:** continue to attempt every in-scope Tier 3
|
|
38
|
+
command. For an entry requiring `db`, `http`, or `external`, record non-PASS
|
|
39
|
+
as a Tier 3 `advisory` command, add a user-owned Residual Risk and exact
|
|
40
|
+
Recommended Next Step, and include it in Could-Not-Verify. It
|
|
41
|
+
MUST NOT create an Acceptance Blocker, conditional acceptance condition, blocked verdict, or
|
|
42
|
+
non-release routing by itself. `io`-only non-PASS and malformed/undeclared
|
|
43
|
+
conformance contracts remain acceptance-gating.
|
|
44
|
+
Enforcement: `scripts/okstra_ctl/conformance.py` (`decide_conformance_gate`)
|
|
45
|
+
performs the core advisory classification/reduction;
|
|
46
|
+
`validators/validate-run.py` (`_validate_conformance`) routes that status to
|
|
47
|
+
warnings instead of failures; and
|
|
48
|
+
`tests/contract/test_okstra_ctl_conformance.py` (`test_external_non_pass_is_advisory`),
|
|
49
|
+
`tests/contract/test_validate_run_conformance.py` (`test_external_non_pass_returns_warning_not_failure`),
|
|
50
|
+
and `tests/contract/test_final_report_contracts.py` (`test_external_qa_advisory_renders_without_downgrading_accepted_verdict`)
|
|
51
|
+
lock the core, gate, and accepted-report behavior respectively.
|
|
37
52
|
- **Manual user test:** read only the source implementation report's `implementation.manualUserTest`. Execute reproducible steps and record `pass`, `fail`, or `blocked` with observed evidence. Human-only or environment-unavailable steps remain `blocked` with the exact reason. Reaffirm an `applicable=false` exemption; do not execute planning `designPreparation` or manual-test PREP items directly.
|
|
38
53
|
- **Design-preparation carry-in:** read only source implementation report `missingInformation` rows whose `source` starts with `design-prep:`. Recommend `ifStillOpen: block` as an acceptance blocker and `ifStillOpen: follow-up` as residual risk. This phase does not read planning PREP sidecars directly or mutate planning snapshots.
|
|
39
54
|
- **Could-not-verify honesty:** use `not-configured`, `env-unavailable`, `rejected`, `gap`, or `blocked` as appropriate. Never convert unavailable evidence into an executed/pass claim.
|
|
40
55
|
- **Source-mutation prohibition:** verification may write only assigned okstra run artifacts. Do not edit source, schema, deployment, lockfile, or configuration files; route detected defects to a later phase.
|
|
41
56
|
- Required deliverable shape (final report, in addition to the standard sections):
|
|
42
|
-
- **Source Implementation Report(s)
|
|
57
|
+
- **Source Implementation Report(s)** (**Enforced:** `validators/validate-run.py` `_validate_verification_target_match` compares `verificationScope`, `worktreePath`, `implementationBaseRef`, `capturedHeadSha`, and the `stageReports` stage set against the digest-verified `instruction-set/verification-target.md`; a snapshot whose digest no longer checks out is ignored rather than trusted. `verificationScope` in particular gates both stage-group eligibility and release-handoff routing, so it is not the report's to restate): the `VERIFICATION_TARGET` snapshot verbatim — verification scope, worktree path, base/head refs, the list of stages under verification, and one row per stage citing its originating implementation final-report (`report_path` from `consumers.jsonl`; render `(report_path unrecorded)` when absent). Every analyser prompt carries the same compact target identity (`**Verification scope:** / **Worktree:** / **Verification base ref:** / **Verification head ref:** / **Verification target path:** / **Verification target digest:**`) and reads the sidecar on demand for the complete diff stat. A worker that cannot confirm its analysis ran against that worktree's delivered diff MUST record a `tool-failure`.
|
|
43
58
|
- **Verdict vocabulary**: Section 7 (`Final Verdict`) MUST include a `Verdict Token` field whose value is exactly one of `accepted`, `conditional-accept`, or `blocked`. `conditional-accept` requires an explicit, exhaustive list of conditions; ambiguous verdicts ("looks good", "mostly ready") are not allowed. Each condition MUST be recorded as a row in the **Conditional Acceptance Conditions** deliverable (`id` `CA-NNN`, `condition`, `evidenceRequired`, `blocksReleaseHandoff`). The validator enforces verdict↔deliverable consistency: `accepted` ⇒ zero acceptance blockers, `blocked` ⇒ at least one, `conditional-accept` ⇒ at least one condition, and a `release-handoff` routing recommendation is allowed only when the verdict is `accepted`. **Any Acceptance Blocker therefore forces the verdict off `accepted` (to `conditional-accept` or `blocked`); the gates below cite this rule instead of restating the arithmetic.**
|
|
44
59
|
- **Acceptance Blockers block** (under section 4): one row per blocker with `id`, `severity` (`critical` / `major` / `minor`), evidence (file path, log excerpt, or test output), and the recommended follow-up phase (`error-analysis` or `implementation-planning`). Empty block is acceptable and preferred — render the single line `- No acceptance blockers found.`
|
|
45
60
|
- **Residual Risk block** (under section 4): risks that are not blockers but should be tracked, each with mitigation owner and a trigger that would escalate them to a blocker.
|
|
46
61
|
- **Validation Evidence**: for every requirement in the originating plan or task brief, cite the artifact (commit SHA, test output, log line, MCP SELECT result) that demonstrates coverage. Paraphrased "verified" claims without an artifact are rejected.
|
|
47
|
-
- **Read-only command log**: any pre-existing test/validation command touched during this run MUST be listed with its exact command line and one honest status — `executed` (ran; carries its exit code) / `env-unavailable` (should run but cannot in this environment — missing replica DB, container, or service; carries the reason, never a faked pass) / `not-configured` (no such qa-command tier) / `rejected` (a mutating/denied token — skipped, carries the denied token). A check that could not run locally is recorded as `env-unavailable`
|
|
48
|
-
- **Could-not-verify roll-up (§5.8.9)**: the template mechanically aggregates every not-confirmed check into one scannable list — `gap` requirement-coverage rows, `not-configured` / `env-unavailable` / `rejected` command rows, and `blocked` manual tests. You do not hand-author it, but you MUST give those rows their honest status so nothing unverified hides across sections: a check silently recorded as `executed`/`covered` will not surface in the roll-up. This is okstra's answer to "say what could not be verified this run."
|
|
62
|
+
- **Read-only command log**: any pre-existing test/validation command touched during this run MUST be listed with its exact command line and one honest status — `executed` (ran; carries its exit code) / `advisory` (external Tier 3 did not PASS; carries observed/expected results and remains user-owned) / `env-unavailable` (should run but cannot in this environment — missing replica DB, container, or service; carries the reason, never a faked pass) / `not-configured` (no such qa-command tier) / `rejected` (a mutating/denied token — skipped, carries the denied token). A check that could not run locally is recorded as `env-unavailable` or `advisory` according to the external QA policy — never silently dropped and never reported as `executed` with an invented exit code. Mutating-command prohibition is the shared read-only boundary (see Non-goals); it is not restated per row.
|
|
63
|
+
- **Could-not-verify roll-up (§5.8.9)**: the template mechanically aggregates every not-confirmed check into one scannable list — `gap` requirement-coverage rows, `advisory` / `not-configured` / `env-unavailable` / `rejected` command rows, and `blocked` manual tests. You do not hand-author it, but you MUST give those rows their honest status so nothing unverified hides across sections: a check silently recorded as `executed`/`covered` will not surface in the roll-up. This is okstra's answer to "say what could not be verified this run."
|
|
49
64
|
- **Routing recommendation**: the next safe phase — one of `release-handoff`, `done`, `error-analysis`, `implementation-planning` — tied to the verdict and blocker list. `release-handoff` is allowed ONLY when the Verdict Token is `accepted`. `release-handoff` is additionally allowed ONLY when the verification scope (the `Verification scope:` line of the injected `VERIFICATION_TARGET` block, recorded as the report's `verificationScope` field) is `whole-task`; a `single-stage` accepted run routes to `release-handoff(stage-group)` (or `implementation` / `done`); plain `release-handoff` remains whole-task-only. Enforcement: `validators/validate-run.py` rejects a `single-stage` report whose routing cites plain `release-handoff`.
|
|
50
|
-
- **Verified-row recording** (single-stage scope only): when the Verdict Token is `accepted`, the lead MUST run `okstra handoff record-verified --plan-run-root <plan-run-root> --stage <N> --report-path <final-report.md path> --data-json <final-report data.json path>` and quote the command + exit code in the report. The helper re-validates taskType/scope/verdict from data.json, so a non-accepted or whole-task report is rejected at the tool layer.
|
|
65
|
+
- **Verified-row recording** (single-stage scope only): when the Verdict Token is `accepted`, the lead MUST run `okstra handoff record-verified --plan-run-root <plan-run-root> --stage <N> --report-path <final-report.md path> --data-json <final-report data.json path>` and quote the command + exit code in the report. The helper re-validates taskType/scope/verdict from data.json, so a non-accepted or whole-task report is rejected at the tool layer. **Enforced:** `validators/validate-run.py` `_validate_verified_row_recorded` requires a `verified` row in `runs/implementation-planning/consumers.jsonl` for every accepted stage — the helper validated its own inputs but nothing checked it had ever run, leaving reports that said `accepted` while the registry said unverified, so the stage was never offered for a stage-group PR.
|
|
51
66
|
- Clarification request policy (phase-specific addendum — shared policy is in `_common-contract.md`):
|
|
52
67
|
- populate `## 1. Clarification Items` only when a blocker hinges on information only the user can supply (deployment intent, intended target environment, business-rule interpretation); use `Blocks=next-phase` for items that gate continuing to release-handoff
|
|
53
68
|
- Self-review pass before finalising the report (`Claude lead` runs this; do not delegate to a generic subagent):
|
|
54
69
|
1. **Verdict precision** — section 7 (`Final Verdict`) includes `Verdict Token` with one of the three allowed verdict tokens; `conditional-accept` lists every condition as an actionable item.
|
|
55
70
|
2. **Blocker traceability** — every blocker cites a concrete artifact (file:line, log excerpt, test exit code, MCP SELECT). Blockers without evidence are demoted to residual risk or removed.
|
|
56
|
-
3. **Coverage check** — every requirement in the originating plan/task brief is either marked covered (with artifact) or listed as a blocker. No silent omissions.
|
|
71
|
+
3. **Coverage check** — every requirement in the originating plan/task brief is either marked covered (with artifact) or listed as a blocker. A user-owned external advisory gap is the sole outcome exception: keep it as a Residual Risk with the exact rerun Recommended Next Step instead of promoting it to a blocker. No silent omissions.
|
|
57
72
|
4. **Verifier dissent preserved** — if workers reach different verdicts, the disagreement is visible in section 1.2; synthesis hides nothing.
|
|
58
73
|
5. **No source-mutation audit** — scan the run's session transcripts for Edit / Write or state-mutating Bash commands that touch paths OUTSIDE `<PROJECT_ROOT>/.okstra/**` and outside the assigned run-artifact paths. Writes to worker prompts, audit sidecars, team-state, the final-report `data.json`, and rendered reports under the run directory are allowed okstra artifacts. Any source/schema/deployment mutation means the run has crossed into implementation and MUST be re-routed; do NOT silently strip the evidence.
|
|
59
74
|
- Cross-verification mode:
|
|
@@ -69,7 +69,11 @@
|
|
|
69
69
|
- writing artifacts anywhere except `<PROJECT_ROOT>/.okstra/` — the run's `reports/` directory is the canonical location for this phase
|
|
70
70
|
- Clarification request policy (phase-specific addenda — shared policy is in `_common-contract.md`):
|
|
71
71
|
{{INCLUDE:_clarification-recommendation.md}}
|
|
72
|
-
- **Evidence note required inside `Statement`**: every clarification row includes `Evidence checked: <path:line>` or `Evidence checked: none — <human-only reason>` in the `Statement` cell. `none` is allowed ONLY when the row's nature is "only a human can answer this" (reporter intent, business priority, organisational decision). A row with `none` that *could* have been answered by the codebase is a defect of this phase, restated from the pre-planning rule above.
|
|
72
|
+
- **Evidence note required inside `Statement`**: every clarification row includes `Evidence checked: <path:line>` or `Evidence checked: none — <human-only reason>` in the `Statement` cell. `none` is allowed ONLY when the row's nature is "only a human can answer this" (reporter intent, business priority, organisational decision). A row with `none` that *could* have been answered by the codebase is a defect of this phase, restated from the pre-planning rule above. **Enforced:** `validators/validate-run.py` `_validate_clarification_evidence_note` requires the note to exist and to be either a concrete `<path>:<line>` or the `none — <reason>` form. The check proves the note exists, not that the lookup happened — a false `none` is caught by review, but it is now an attributable claim rather than a silent omission.
|
|
73
|
+
- **`Blocks=approval` admissibility (scope boundary).** Requirement Coverage has an admissibility test and Clarification rows did not, so anything at all could acquire approval-blocking force — a prose inconsistency inside a *rejected* option blocked a plan with the same weight as a business decision. A row earns `Blocks=approval` only when, **absent an answer, the `implementation` run would produce wrong or unsafe code**. Apply the test to the row's consequence, not its topic:
|
|
74
|
+
- admissible — the answer selects between behaviours the code must implement, fixes a requirement the plan would otherwise satisfy incorrectly, or resolves a safety/data-integrity question.
|
|
75
|
+
- NOT admissible → use `Blocks=none` — QA-harness or tooling scope, report notation and wording, numbering or citation-range cleanup, anything inside an option the plan does not recommend, and anything the codebase answers (which the codebase-first rule already forbids raising at all). These belong in `## 5. Missing Information and Risks` or a Working Assumption; they are recorded, not gating.
|
|
76
|
+
- A row you would answer with "the plan would still produce the same code either way" is by construction `Blocks=none`.
|
|
73
77
|
- Section heading contract (BLOCKING — validator scans for these literal English substrings):
|
|
74
78
|
- The final report MUST include section headings containing each of the following exact strings — this list mirrors `PLANNING_REQUIRED_SECTIONS` in `validators/validate-run.py`, which is the SSOT: `Option Candidates`, `Trade-off`, `Recommended Option`, `Stage Map`, `Stepwise Execution Order`, `Dependency`, `Validation Checklist`, `Rollback`, `Requirement Coverage`, `Plan Body Verification`, `Cross-Project Dependencies`, `Decision Drafts`. (Approval is no longer a body section — it is the YAML frontmatter `approved` field.) Three further headings are enforced elsewhere, not by that scan: `Stage Exit Contract` and `Stage Validation` are per-stage subsections checked by `validators/validate-implementation-plan-stages.py`, and `Implementation Design Preparation` is enforced and rendered by `schemas/final-report-v1.0.schema.json` plus `templates/reports/final-report.template.md` from its required data block.
|
|
75
79
|
- Korean translations are allowed in parentheses (e.g. `### Recommended Option (Korean gloss)`), but the English keyword must be present verbatim in the heading line.
|
|
@@ -78,6 +82,11 @@
|
|
|
78
82
|
- Required deliverable shape (final report, in addition to the standard sections):
|
|
79
83
|
- at least two implementation options. **Each option must include**:
|
|
80
84
|
- **File Structure**: an explicit list of files to create / modify / delete with each file's responsibility (one-line each). Use the form `Create: path — responsibility` / `Modify: path:line-range — change summary` / `Delete: path — reason`. Write every `path` in full and `<PROJECT_ROOT>`-relative — never ellipsis-abbreviated (`…` / `...` / a trailing `/…`); an abbreviated path does not resolve and is rejected by plan-body verification as a kind-b path mismatch.
|
|
85
|
+
- **Two-tier change description.** Each `fileStructure` row carries `summary` **and** optional `details`, and they are not interchangeable:
|
|
86
|
+
- `summary` — one plain-language sentence a reviewer who has never opened this file can follow: what changes and why it is needed. Name behaviour and domain nouns, not identifiers. No function/type/variable names, no call chains, no line numbers, no conditional logic spelled out. **Enforced:** `schemas/final-report-v1.0.schema.json` `$defs.OptionCandidate.fileStructure.items.summary` caps it at 120 characters, so a dense identifier dump fails schema validation.
|
|
87
|
+
- `details` — the technical specifics that used to be crammed into `summary`: exact symbol names, signatures, comparison semantics, line ranges, enum members. Omit the field when there is nothing beyond the summary.
|
|
88
|
+
- Bad `summary` (identifier dump, no readable claim): `Widen the return of retrieveFontFamilyGroupWithFontVersionId to also yield the matched FontFamily (own key and enabled flag) and the matched Font.status.`
|
|
89
|
+
- Good — `summary`: `Carry enough information out of the font lookup to tell whether the matched family is switched off.` / `details`: `Add the matched FontFamily (own key + enabled) and Font.status to the return of retrieveFontFamilyGroupWithFontVersionId. Both are already in scope in the :102-104 loop and dropped at return.`
|
|
81
90
|
- affected interfaces / public contracts and downstream consumers
|
|
82
91
|
- estimated blast radius (units, configs, deployment manifests, data migrations)
|
|
83
92
|
- trade-off matrix across options (rows = options, columns at minimum: complexity, risk, reversibility, test coverage cost, rollout cost)
|
|
@@ -100,7 +109,15 @@
|
|
|
100
109
|
- **Per-stage conformance declaration (mandatory one line, in the stage section — same placement freedom as `TDD exemption:`):** the stage MUST carry exactly one of:
|
|
101
110
|
- `Conformance tests: stage-<N> — <task_root>/qa/scripts/stage-<N>.<ext> (requires=[db|io|http|external,...])` — a Tier3 verification script that proves this stage's upstream requirements (brief / requirements-discovery / error-analysis / improvement-discovery → this stage's `Acceptance`) hold against **real** DB rows, real endpoints, or the real external API — NOT mocks. When you emit this line you MUST also (a) write the script to `<task_root>/qa/scripts/stage-<N>.<ext>` and (b) add a matching entry to `<task_root>/qa/conformance-manifest.json` with fields `stageKey` (= `<task-id>-stage-<N>`), `script`, `runCommand`, `requirementIds`, `requires` (subset of `{db, io, http, external}`), `passContract`, `exemption: null`, `waiver: null`. The script's standard interface: a `main` that exits `0`=PASS / non-zero=FAIL, and whose stdout ends with `QA-RESULT: PASS|FAIL` followed by one `REQ <id>: PASS|FAIL: <reason>` line per requirement. When the verification body is a test spec, author it with the project's own test framework (devDependency) invoked via a discovery override at `<task_root>/qa/scripts/` (jest: `--config <project config> --roots <task_root>/qa/scripts`) — never hand-roll `describe`/`expect` and never widen the project's own test config; for TypeScript specs also write `<task_root>/qa/scripts/tsconfig.json` extending the project tsconfig with the runner's `types` entry so editors resolve the file.
|
|
102
111
|
- `Conformance exemption: <reason>` — only for stages that touch no db/io/http/external surface, or where unit tests fully cover the increment. (If the eventual `implementation` diff actually touches one of those surfaces, `validate-run.py`'s diff-surface cross-check is BLOCKING — an exemption cannot hide a real db/io/http/external change.)
|
|
103
|
-
|
|
112
|
+
- **External QA outcome guideline:** after satisfying the mandatory script,
|
|
113
|
+
manifest, and S11 declarations above, an entry whose `requires` contains
|
|
114
|
+
`db`, `http`, or `external` should document the exact `runCommand`,
|
|
115
|
+
prerequisites, requirement IDs, and expected `QA-RESULT`. Okstra may start the
|
|
116
|
+
environment and run it automatically, but `FAIL`, missing evidence, or an
|
|
117
|
+
unavailable environment is a user-owned follow-up, never a plan approval or
|
|
118
|
+
later run blocker. `requires=[]` and `requires=[io]` remain blocking.
|
|
119
|
+
Remote IO should also declare `external`.
|
|
120
|
+
The manifest lives at the **task level** (`<task_root>/qa/`, path token `TASK_QA_PATH`) and is shared across planning → implementation → final-verification. Layout split: executable scripts (conformance + any real-IO test) live under `<task_root>/qa/scripts/`; data sidecars (`conformance-manifest.json`, `result-*.json`) stay at the `qa/` root. This declaration is enforced at four layers: `validators/validate-implementation-plan-stages.py` check **S11** forces every stage to carry one of the two lines; at the planning boundary `validators/validate-run.py` `_validate_planning_conformance_declared` fails when a stage that declared `Conformance tests:` has no matching `-stage-<N>` entry in the shared manifest (a declaration that was never materialized); the manifest JSON structure — including each entry's `script` living under `qa/scripts/` — is enforced by `validate_conformance_manifest` (called from both the run path and validate-run); and the result policy is evaluated by `conformance.py` and `validate-run.py`.
|
|
104
121
|
- `### Stage Exit Contract` — predicted added/modified files, newly exposed identifiers/types/endpoints, downstream-usable resources.
|
|
105
122
|
- `### Stage Validation` — pre / mid / post exact commands or observable outcomes for this stage only.
|
|
106
123
|
- **Vertical-slice-first partition rule (1st-class):** the grouping anchor is a **thin end-to-end vertical slice** — one stage delivers a single user-observable increment, crossing whatever layers are needed (data → service → API → UI) to make that one increment work. File/module proximity is demoted to the **intra-slice grouping rule**: within a slice, keep steps touching the same file/directory/module together so the diff, PR, and rollback unit stay cohesive. **Horizontal layer-splitting is forbidden** — never carve "the DB layer" into one stage and "the service layer" into the next; that produces stages that ship no standalone user value. A stage is split ONLY when (a) a real `depends-on` data/contract dependency exists, (b) effective steps would exceed 8, or (c) it is a distinct vertical slice (a different user-value increment). Maximising the number of parallel stages is NOT a reason to split — parallelism is an emergent property of independent stages, never a partitioning goal.
|
|
@@ -128,7 +145,7 @@
|
|
|
128
145
|
- **The reach of this gate — do not over-trust it.** What is mechanically enforced is the *form* of each source, that a `brief:` heading literally exists, that a `derived:` chain terminates without cycling, and that no stage is uncited. What is **not** enforced is whether the cited source genuinely demands the requirement. Every brief carries the same generic headings (`## Acceptance Criteria`, `## Context`, …), so attaching invented work to a real heading still parses clean. The gate's value is that it forces every item to name an origin and makes fabrication explicit and auditable — judging whether that origin actually demands the item remains a reviewer / `DISAGREE(f)` responsibility, and passing this gate is never evidence that the scope is justified.
|
|
129
146
|
- **Stage citation format:** the reverse check reads each `Covered by` cell as prose, so a stage counts as cited only when its number is anchored to a `Stage` / `Stages` word on the same line. These all read: `Stage 2`; `Stage 1, Stage 2, Stage 3`; `Stages 1, 2, 3`; `Stages 1, 2, and 3`; ranges (`Stages 1-3`, `Stages 1 to 3`, `Stages 1 through 3`); and `and` / `&` conjunctions. A bare number with no `stage` word anchoring it is NOT read as a citation, so `covered by the recommended option, step 4` cites nothing. **Enforced:** `validators/validate-run.py` `_validate_stage_has_requirement` fails the plan when any Stage Map stage is cited by no coverage row.
|
|
130
147
|
- Because that reader only sees prose, it cannot tell a citation from a mention: `Stage 1 (superseded by Stage 2)` still counts Stage 1 as cited, and one row citing `Stages 1-64` rubber-stamps every stage in the map. Cite the stages a requirement is actually satisfied by — not stages merely mentioned, and never a blanket range standing in for the work of checking.
|
|
131
|
-
- **Requirement Coverage (mandatory, §5.5.8):** one row per concrete requirement from the task brief / packet. Assign stable IDs `R-001`, `R-002`, ... in source order. Columns: `ID | Source | Requirement | Covered by option / stage / step | Status`. `Source` follows the three-form grammar defined in the **Scope provenance** rule above — a free-form `file:line` is not one of the three forms and is rejected. When this run's brief is a fan-out packet, the task manifest's `taskBriefPath` points at that packet file, so `brief:` cites the packet's own headings (`## Scope`, `## Evidence`, `## Requirement Provenance`) — not the headings of the upstream user brief the packet came from. For `covered`, `Covered by` must name the specific Option Candidate and Stage/Step that satisfies it, not just "recommended option". **Enforced:** `validators/validate-run.py` `_validate_requirement_coverage_covered_by` fails a `covered` row whose `coveredBy` is bare "recommended option", names no Option/Stage/Step anchor, or cites a Stage number absent from the Stage Map (whether the cited step *actually satisfies* the requirement remains a worker `DISAGREE(f)` judgment). `Status` is one of `covered`, `gap`, `blocked C-NNN`, or `documented-deviation`. A deviation records the concrete alternative in `coveredBy`, non-empty unique `decisionRefs` (`C-NNN` clarification IDs and/or `D-NNNN` decision-draft numbers), and `approvalDisposition: accepted|blocked C-NNN`. `accepted` is valid only when a referenced clarification is user-confirmed (`answered|resolved` with non-empty `userInput`); `blocked C-NNN` is valid only when that same-report clarification is `open` and `Blocks=approval`. **Enforced:** schema `$defs.ImplementationRequirementCoverageRow` plus `validators/validate-run.py` `_validate_requirement_deviations`; the exact `P-Req-*` queue still comes from `scripts/okstra_ctl/plan_items.py`. If any row is `gap`, plain `blocked C-NNN`, or a deviation whose approval disposition is blocked, the Plan Body Verification gate MUST NOT be `passed` / `passed-with-dissent`; add a matching `Blocks=approval` row for the blocker and keep `approved: false
|
|
148
|
+
- **Requirement Coverage (mandatory, §5.5.8):** one row per concrete requirement from the task brief / packet. Assign stable IDs `R-001`, `R-002`, ... in source order. Columns: `ID | Source | Requirement | Covered by option / stage / step | Status`. `Source` follows the three-form grammar defined in the **Scope provenance** rule above — a free-form `file:line` is not one of the three forms and is rejected. When this run's brief is a fan-out packet, the task manifest's `taskBriefPath` points at that packet file, so `brief:` cites the packet's own headings (`## Scope`, `## Evidence`, `## Requirement Provenance`) — not the headings of the upstream user brief the packet came from. For `covered`, `Covered by` must name the specific Option Candidate and Stage/Step that satisfies it, not just "recommended option". **Enforced:** `validators/validate-run.py` `_validate_requirement_coverage_covered_by` fails a `covered` row whose `coveredBy` is bare "recommended option", names no Option/Stage/Step anchor, or cites a Stage number absent from the Stage Map (whether the cited step *actually satisfies* the requirement remains a worker `DISAGREE(f)` judgment). `Status` is one of `covered`, `gap`, `blocked C-NNN`, or `documented-deviation`. A deviation records the concrete alternative in `coveredBy`, non-empty unique `decisionRefs` (`C-NNN` clarification IDs and/or `D-NNNN` decision-draft numbers), and `approvalDisposition: accepted|blocked C-NNN`. `accepted` is valid only when a referenced clarification is user-confirmed (`answered|resolved` with non-empty `userInput`); `blocked C-NNN` is valid only when that same-report clarification is `open` and `Blocks=approval`. **Enforced:** schema `$defs.ImplementationRequirementCoverageRow` plus `validators/validate-run.py` `_validate_requirement_deviations`; the exact `P-Req-*` queue still comes from `scripts/okstra_ctl/plan_items.py`. If any row is `gap`, plain `blocked C-NNN`, or a deviation whose approval disposition is blocked, the Plan Body Verification gate MUST NOT be `passed` / `passed-with-dissent`; add a matching `Blocks=approval` row for the blocker and keep `approved: false`, and record `coverage-gap` in `planBodyVerification.gateBlockedBy` so the blocking value does not silently read as a worker disagreement. **Exception — no double counting:** a row whose `blocked C-NNN` cites a clarification that *this same run's* §5.5.9 round promoted from a `majority-disagree` item is not an independent blocker; that blocker is already counted as the plan item, and re-counting it makes the run block on a clarification it just authored and carries the row into the next run as a fresh blocker. **Enforced:** `validators/validate-run.py` `_validate_gate_blocked_by` (fails a passing gate with a blocking coverage row) and `_independent_coverage_blockers` (the same-run exclusion).
|
|
132
149
|
- **Review-rule compliance plan:** when a project-local review rule pack is found, each Option Candidate MUST include the design implication of those rules in its File Structure / interfaces / blast-radius notes. For any helper or data transform used by more than one changed service, the plan must either place it in a shared module or explicitly justify why duplication is intentional. For any test step, the plan must state the observable behavior being asserted, not the internal collaborator call being pinned. For any exported/public method added or renamed, the step must carry the intended noun/side-effect semantics so implementation names can be reviewed before code is written.
|
|
133
150
|
- the YAML frontmatter MUST include the line `approved: false` (report-writer always emits the unflipped value). The user authorises the next `implementation` run by flipping it to `approved: true` (manual edit or `--approve` CLI). Do NOT recreate any `User Approval Request` body block — the validator fails reports that contain one (see `validators/validate-run.py` deprecated patterns).
|
|
134
151
|
- the YAML frontmatter MUST include the line `implementation-option:` directly under `approved:` (report-writer always emits it with an **empty value**). The user selects which Option Candidate the next `implementation` run executes by filling this line with that option's name (manual edit or `--implementation-option <name>` CLI). When left empty, the `implementation` run falls back to the `Recommended Option`.
|
|
@@ -142,7 +159,7 @@
|
|
|
142
159
|
```
|
|
143
160
|
|
|
144
161
|
An `AGREE` note records the counterexample considered and its exclusion reason. If the judgement needs unavailable external material, record `verification-error`, not `DISAGREE`. **Enforced:** `validators/validate-run.py` `_validate_plan_item_extraction_completeness` compares the exact deterministic set, independently rejecting missing, unexpected, and duplicate plan-item IDs, including `P-Prep-*`.
|
|
145
|
-
- **§5.5.9 Plan Body Verification (BLOCKING).** After report-writer finishes the draft, the lead MUST run a worker peer-review round on the consolidated plan body (Option Candidates / Trade-off Matrix / Recommended Option / Stage Map and per-stage sections / Dependency / Validation Checklist / Rollback / Requirement Coverage) and populate `### 5.5.9 Plan Body Verification` in the final report. The round protocol, plan-item ID scheme (`P-Opt-*` / `P-Step-*` / `P-Dep-*` / `P-Val-*` / `P-Rb-*`), verdict semantics, gate-result classification, and dissent log format are defined in `prompts/lead/plan-body-verification.md`. The four gate-result values are `passed`, `passed-with-dissent`, `blocked-by-disagreement`, `aborted-non-result`. When the gate would have been `blocked-by-disagreement` or `aborted-non-result`, the lead MUST NOT silently flip it to one of the passing values to "unblock" the run — that is a contract violation. **Enforced:** `validators/validate-run.py` `_validate_plan_body_gate_recompute` re-derives the gate from `planItems[].verdicts` and fails when the declared `gateResult` claims a healthier outcome than the recorded votes support; `_validate_plan_item_extraction_completeness` fails when any plan-body deliverable category is under-extracted into `planItems`, so a dropped item can no longer dodge the gate. When `convergence.adversarial=true` (the default for this phase), this round uses the adversarial posture — verifiers confirm cited paths/commands and the burden of proof is on the plan — but the gate threshold stays `majority-disagree` (see that skill's §"Adversarial plan-body posture"). Among the majority-disagree items, those that are majority-`planner-fixable` go through
|
|
162
|
+
- **§5.5.9 Plan Body Verification (BLOCKING).** After report-writer finishes the draft, the lead MUST run a worker peer-review round on the consolidated plan body (Option Candidates / Trade-off Matrix / Recommended Option / Stage Map and per-stage sections / Dependency / Validation Checklist / Rollback / Requirement Coverage) and populate `### 5.5.9 Plan Body Verification` in the final report. The round protocol, plan-item ID scheme (`P-Opt-*` / `P-Step-*` / `P-Dep-*` / `P-Val-*` / `P-Rb-*`), verdict semantics, gate-result classification, and dissent log format are defined in `prompts/lead/plan-body-verification.md`. The four gate-result values are `passed`, `passed-with-dissent`, `blocked-by-disagreement`, `aborted-non-result`. When the gate would have been `blocked-by-disagreement` or `aborted-non-result`, the lead MUST NOT silently flip it to one of the passing values to "unblock" the run — that is a contract violation. **Enforced:** `validators/validate-run.py` `_validate_plan_body_gate_recompute` re-derives the gate from `planItems[].verdicts` and fails when the declared `gateResult` claims a healthier outcome than the recorded votes support; `_validate_plan_item_extraction_completeness` fails when any plan-body deliverable category is under-extracted into `planItems`, so a dropped item can no longer dodge the gate. When `convergence.adversarial=true` (the default for this phase), this round uses the adversarial posture — verifiers confirm cited paths/commands and the burden of proof is on the plan — but the gate threshold stays `majority-disagree` (see that skill's §"Adversarial plan-body posture"). Among the majority-disagree items, those that are majority-`planner-fixable` go through report-writer self-fix rounds (`prompts/lead/plan-body-verification.md` "Self-fix round"). A `planner-fixable` item that survives the budget is **not** promoted to the user — it becomes a Working Assumption in `## 5. Missing Information and Risks` and folds into `passed-with-dissent`, because a defect the planner could have fixed is not a user decision. Only majority-`needs-user-input` items, and correctness-critical defects (`DISAGREE` kinds `a` / `d`, or `f` on `P-Req-*`) regardless of fixability, become `Blocks=approval` clarifications. `validators/validate-run.py` `_validate_self_fix_before_clarification` fails a planner-fixable majority item promoted without a self-fix as `contract-violated`.
|
|
146
163
|
- **Decision-record evaluation (sole owner)**: this phase is the **single owner** of decision-record evaluation in the okstra lifecycle. The brief never evaluates or drafts decision records — it only forwards `adr-candidate:*` signals. Every `adr-candidate:*` entry inherited from the brief's `Open Questions` is a mandatory evaluation target. In addition, evaluate every decision the recommended option introduces against the three criteria:
|
|
147
164
|
1. **Hard to reverse** — would changing the decision later cost meaningfully more than deciding now?
|
|
148
165
|
2. **Surprising without context** — would a future reader, seeing only the code, wonder "why was it built this way?"?
|
|
@@ -830,9 +830,40 @@ def _implementation_executor_tail(
|
|
|
830
830
|
selfcheck_path = profiles / "_implementation-self-check.md"
|
|
831
831
|
if selfcheck_path.is_file():
|
|
832
832
|
parts.append(selfcheck_path.read_text(encoding="utf-8"))
|
|
833
|
+
parts.extend(_clarification_response_section(manifest, active_context, project_root))
|
|
833
834
|
return "\n\n".join(parts)
|
|
834
835
|
|
|
835
836
|
|
|
837
|
+
def _clarification_response_section(
|
|
838
|
+
manifest: Mapping[str, Any],
|
|
839
|
+
active_context: Mapping[str, Any],
|
|
840
|
+
project_root: Path,
|
|
841
|
+
) -> list[str]:
|
|
842
|
+
"""The user's answers, inlined for a CLI executor.
|
|
843
|
+
|
|
844
|
+
A CLI worker runs in its own process and receives only the prompt text, so
|
|
845
|
+
a path reference never reaches it. Passing the clarification response as a
|
|
846
|
+
path meant the executor implemented the pre-answer plan while the report
|
|
847
|
+
described the answers as carried in — the user answered and nothing
|
|
848
|
+
downstream acted on it.
|
|
849
|
+
"""
|
|
850
|
+
relative = _instruction_path(manifest, active_context, "clarificationResponsePath")
|
|
851
|
+
if not relative:
|
|
852
|
+
return []
|
|
853
|
+
path = _resolve_project_path(project_root, relative)
|
|
854
|
+
if not path.is_file():
|
|
855
|
+
return []
|
|
856
|
+
body = path.read_text(encoding="utf-8").strip()
|
|
857
|
+
if not body:
|
|
858
|
+
return []
|
|
859
|
+
return [
|
|
860
|
+
"# Clarification answers carried in (authoritative)\n\n"
|
|
861
|
+
"The user answered these before this run. Where an answer conflicts "
|
|
862
|
+
"with the approved plan text, the answer wins — implement the answer "
|
|
863
|
+
f"and say so in your result.\n\nSource: `{relative}`\n\n{body}"
|
|
864
|
+
]
|
|
865
|
+
|
|
866
|
+
|
|
836
867
|
def _resolve_report_language(
|
|
837
868
|
project_root: Path,
|
|
838
869
|
manifest: Mapping[str, Any],
|
|
@@ -16,6 +16,23 @@ from dataclasses import dataclass
|
|
|
16
16
|
|
|
17
17
|
# diff 가 건드린 표면과 대조할 capability 태그 화이트리스트.
|
|
18
18
|
CAPABILITY_WHITELIST: tuple[str, ...] = ("db", "io", "http", "external")
|
|
19
|
+
EXTERNAL_ADVISORY_CAPABILITIES: frozenset[str] = frozenset(
|
|
20
|
+
{"db", "http", "external"}
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def is_advisory_conformance_entry(entry: object) -> bool:
|
|
25
|
+
"""Return whether one entry depends on user-owned external QA."""
|
|
26
|
+
if not isinstance(entry, dict):
|
|
27
|
+
return False
|
|
28
|
+
requires = entry.get("requires")
|
|
29
|
+
if not isinstance(requires, list):
|
|
30
|
+
return False
|
|
31
|
+
return bool(
|
|
32
|
+
EXTERNAL_ADVISORY_CAPABILITIES.intersection(
|
|
33
|
+
capability for capability in requires if isinstance(capability, str)
|
|
34
|
+
)
|
|
35
|
+
)
|
|
19
36
|
|
|
20
37
|
|
|
21
38
|
def _check_nonempty_str(value: object, path: str, errors: list[str]) -> bool:
|
|
@@ -138,9 +155,9 @@ def parse_qa_result(stdout: str) -> QaResult:
|
|
|
138
155
|
@dataclass
|
|
139
156
|
class ConformanceVerdict:
|
|
140
157
|
stage_key: str
|
|
141
|
-
status: str # "PASS" | "BLOCKING" | "WAIVED" | "EXEMPT"
|
|
142
|
-
ok: bool #
|
|
143
|
-
conditional: bool #
|
|
158
|
+
status: str # "PASS" | "ADVISORY" | "BLOCKING" | "WAIVED" | "EXEMPT"
|
|
159
|
+
ok: bool # True when the gate permits progress.
|
|
160
|
+
conditional: bool # True only for a user-confirmed blocking-entry waiver.
|
|
144
161
|
message: str
|
|
145
162
|
|
|
146
163
|
|
|
@@ -152,21 +169,24 @@ def _waiver_covers_entry(waiver: dict, entry: dict) -> bool:
|
|
|
152
169
|
요구하는 모든 capability(`requires`)가 scope 안에 들어올 때만 entry 가 완전히
|
|
153
170
|
가려진다. scope 밖 capability 를 entry 가 건드리면 waiver 가 덮지 못하므로
|
|
154
171
|
결과로 정상 게이트해야 한다(예: `db` 만 waive 한 run 에서 `http` 는 계속 게이트)."""
|
|
172
|
+
requires = entry.get("requires")
|
|
173
|
+
required = {c for c in requires if isinstance(c, str)} if isinstance(requires, list) else set()
|
|
174
|
+
if not required:
|
|
175
|
+
return False
|
|
155
176
|
scope = waiver.get("scope")
|
|
156
177
|
if not isinstance(scope, list) or not scope:
|
|
157
178
|
return True
|
|
158
|
-
requires = entry.get("requires")
|
|
159
|
-
required = {c for c in requires if isinstance(c, str)} if isinstance(requires, list) else set()
|
|
160
179
|
return required.issubset({c for c in scope if isinstance(c, str)})
|
|
161
180
|
|
|
162
181
|
|
|
163
182
|
def decide_conformance_gate(entry: dict, result: object) -> ConformanceVerdict:
|
|
164
|
-
"""
|
|
183
|
+
"""Determine the gate verdict from one stage entry and its result.
|
|
165
184
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
185
|
+
Precedence is exemption, advisory waiver, blocking waiver, then result
|
|
186
|
+
evaluation. A PASS result passes. For unexecuted, missing, or failed results,
|
|
187
|
+
external-capability entries are ADVISORY with user-owned follow-up; all other
|
|
188
|
+
entries are BLOCKING. Manifest validation already guarantees valid exemption
|
|
189
|
+
and waiver shapes.
|
|
170
190
|
"""
|
|
171
191
|
key = entry.get("stageKey", "<unknown>")
|
|
172
192
|
exemption = entry.get("exemption")
|
|
@@ -175,8 +195,15 @@ def decide_conformance_gate(entry: dict, result: object) -> ConformanceVerdict:
|
|
|
175
195
|
key, "EXEMPT", True, False,
|
|
176
196
|
f"conformance exempted: {exemption.get('reason', '')}",
|
|
177
197
|
)
|
|
198
|
+
advisory = is_advisory_conformance_entry(entry)
|
|
178
199
|
waiver = entry.get("waiver")
|
|
179
|
-
if waiver and _waiver_covers_entry(waiver, entry):
|
|
200
|
+
if advisory and waiver and _waiver_covers_entry(waiver, entry):
|
|
201
|
+
return ConformanceVerdict(
|
|
202
|
+
key, "ADVISORY", True, False,
|
|
203
|
+
f"external conformance not run by user waiver: "
|
|
204
|
+
f"{waiver.get('reason', '')}",
|
|
205
|
+
)
|
|
206
|
+
if not advisory and waiver and _waiver_covers_entry(waiver, entry):
|
|
180
207
|
return ConformanceVerdict(
|
|
181
208
|
key, "WAIVED", True, True,
|
|
182
209
|
f"conformance waived by {waiver.get('acknowledgedBy', '?')}: "
|
|
@@ -185,6 +212,17 @@ def decide_conformance_gate(entry: dict, result: object) -> ConformanceVerdict:
|
|
|
185
212
|
overall = getattr(result, "overall", None) # None when result is None → "never ran"
|
|
186
213
|
if overall == "PASS":
|
|
187
214
|
return ConformanceVerdict(key, "PASS", True, False, "conformance PASS")
|
|
215
|
+
if advisory:
|
|
216
|
+
if overall is None:
|
|
217
|
+
reason = "external conformance did not run"
|
|
218
|
+
elif overall == "MISSING":
|
|
219
|
+
reason = "external conformance emitted no QA-RESULT marker"
|
|
220
|
+
else:
|
|
221
|
+
reason = f"external conformance {overall}"
|
|
222
|
+
return ConformanceVerdict(
|
|
223
|
+
key, "ADVISORY", True, False,
|
|
224
|
+
f"{reason} (user-owned follow-up; non-blocking)",
|
|
225
|
+
)
|
|
188
226
|
if overall is None:
|
|
189
227
|
return ConformanceVerdict(
|
|
190
228
|
key, "BLOCKING", False, False,
|
|
@@ -292,12 +292,18 @@ def run_finalize(
|
|
|
292
292
|
ctx: FinalizeContext,
|
|
293
293
|
*,
|
|
294
294
|
before_step: Callable[[str], None] | None = None,
|
|
295
|
+
only: Sequence[str] | None = None,
|
|
295
296
|
) -> dict[str, Any]:
|
|
296
297
|
"""Run the Phase 7 steps in order, stopping at the first non-zero exit.
|
|
297
298
|
|
|
298
299
|
``before_step`` fires immediately before each step is spawned, letting an
|
|
299
300
|
adapter settle state the step will read (the Codex adapter marks the report
|
|
300
301
|
writer `completed` before `validate-run` inspects team-state).
|
|
302
|
+
|
|
303
|
+
``only`` restricts the run to the named steps, preserving contractual
|
|
304
|
+
order. The last step (`validate-run`) is the one that usually fails, and
|
|
305
|
+
re-running the whole sequence to retry it repeats three idempotent steps
|
|
306
|
+
at full token and wall-clock cost.
|
|
301
307
|
"""
|
|
302
308
|
steps: list[dict[str, Any]] = []
|
|
303
309
|
try:
|
|
@@ -305,6 +311,17 @@ def run_finalize(
|
|
|
305
311
|
except FinalizeError as exc:
|
|
306
312
|
return {"ok": False, "reason": str(exc), "steps": steps}
|
|
307
313
|
|
|
314
|
+
if only:
|
|
315
|
+
selected = set(only)
|
|
316
|
+
unknown = sorted(selected - set(STEP_ORDER))
|
|
317
|
+
if unknown:
|
|
318
|
+
return {
|
|
319
|
+
"ok": False,
|
|
320
|
+
"reason": f"unknown finalize step(s): {', '.join(unknown)}",
|
|
321
|
+
"steps": steps,
|
|
322
|
+
}
|
|
323
|
+
commands = [(name, cmd) for name, cmd in commands if name in selected]
|
|
324
|
+
|
|
308
325
|
for name, command in commands:
|
|
309
326
|
if before_step is not None:
|
|
310
327
|
before_step(name)
|
|
@@ -374,9 +391,34 @@ def _parser() -> argparse.ArgumentParser:
|
|
|
374
391
|
default="",
|
|
375
392
|
help="defaults to the run manifest's teamStatePath",
|
|
376
393
|
)
|
|
394
|
+
parser.add_argument(
|
|
395
|
+
"--only",
|
|
396
|
+
action="append",
|
|
397
|
+
default=[],
|
|
398
|
+
choices=list(STEP_ORDER),
|
|
399
|
+
help=(
|
|
400
|
+
"run only these steps (repeatable, contractual order preserved). "
|
|
401
|
+
"Use `--only validate-run` to retry the step that usually fails "
|
|
402
|
+
"without repeating the three idempotent ones before it."
|
|
403
|
+
),
|
|
404
|
+
)
|
|
377
405
|
return parser
|
|
378
406
|
|
|
379
407
|
|
|
408
|
+
def _step_summary_lines(result: Mapping[str, Any]) -> list[str]:
|
|
409
|
+
"""One human-readable line per step, so the outcome is legible without
|
|
410
|
+
parsing the JSON payload."""
|
|
411
|
+
lines = []
|
|
412
|
+
for step in result.get("steps") or []:
|
|
413
|
+
code = step.get("exitCode")
|
|
414
|
+
mark = "ok " if code == 0 else "FAIL"
|
|
415
|
+
lines.append(f" [{mark}] {step.get('name')} (exit {code})")
|
|
416
|
+
for name in STEP_ORDER:
|
|
417
|
+
if not any(s.get("name") == name for s in (result.get("steps") or [])):
|
|
418
|
+
lines.append(f" [skip] {name}")
|
|
419
|
+
return lines
|
|
420
|
+
|
|
421
|
+
|
|
380
422
|
def main(argv: Sequence[str] | None = None) -> int:
|
|
381
423
|
args = _parser().parse_args(argv)
|
|
382
424
|
try:
|
|
@@ -384,10 +426,19 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|
|
384
426
|
except FinalizeError as exc:
|
|
385
427
|
print(f"error: {exc}", file=sys.stderr)
|
|
386
428
|
return 2
|
|
387
|
-
result = run_finalize(ctx)
|
|
429
|
+
result = run_finalize(ctx, only=args.only or None)
|
|
388
430
|
print(json.dumps(result, indent=2, ensure_ascii=False))
|
|
431
|
+
print("finalize steps:", file=sys.stderr)
|
|
432
|
+
for line in _step_summary_lines(result):
|
|
433
|
+
print(line, file=sys.stderr)
|
|
389
434
|
if not result["ok"]:
|
|
390
435
|
print(f"error: {result['reason']}", file=sys.stderr)
|
|
436
|
+
if result.get("steps"):
|
|
437
|
+
print(
|
|
438
|
+
"retry just the failing step with "
|
|
439
|
+
f"`--only {result['steps'][-1].get('name')}`",
|
|
440
|
+
file=sys.stderr,
|
|
441
|
+
)
|
|
391
442
|
return 1
|
|
392
443
|
return 0
|
|
393
444
|
|
|
@@ -513,6 +513,7 @@ def _emit_table(lines: list[str], start: int, section_path: list[str]) -> tuple[
|
|
|
513
513
|
)
|
|
514
514
|
|
|
515
515
|
narrow_cols = _narrow_columns(header_cells, rows)
|
|
516
|
+
path_cols = _path_columns(header_cells)
|
|
516
517
|
# `User input` carries the embedded form widget (textarea / select /
|
|
517
518
|
# input) which needs all the horizontal space it can get; its
|
|
518
519
|
# markdown plain text is empty or a short placeholder so the auto
|
|
@@ -523,7 +524,7 @@ def _emit_table(lines: list[str], start: int, section_path: list[str]) -> tuple[
|
|
|
523
524
|
head = (
|
|
524
525
|
"<thead><tr>"
|
|
525
526
|
+ "".join(
|
|
526
|
-
f"<th{_col_class(idx, narrow_cols)}>{_inline(c)}</th>"
|
|
527
|
+
f"<th{_col_class(idx, narrow_cols, path_cols)}>{_inline(c)}</th>"
|
|
527
528
|
for idx, c in enumerate(header_cells)
|
|
528
529
|
)
|
|
529
530
|
+ "</tr></thead>"
|
|
@@ -562,7 +563,7 @@ def _emit_table(lines: list[str], start: int, section_path: list[str]) -> tuple[
|
|
|
562
563
|
)
|
|
563
564
|
else:
|
|
564
565
|
cells_html.append(
|
|
565
|
-
f"<td{_col_class(idx, narrow_cols)}>{_inline(cell)}</td>"
|
|
566
|
+
f"<td{_col_class(idx, narrow_cols, path_cols)}>{_inline(cell)}</td>"
|
|
566
567
|
)
|
|
567
568
|
body_rows.append(
|
|
568
569
|
f'<tr id="{html.escape(meta.row_id.lower())}" '
|
|
@@ -579,7 +580,7 @@ def _emit_table(lines: list[str], start: int, section_path: list[str]) -> tuple[
|
|
|
579
580
|
body_rows.append(
|
|
580
581
|
tr_open
|
|
581
582
|
+ "".join(
|
|
582
|
-
f"<td{_col_class(idx, narrow_cols)}>{_inline(c)}</td>"
|
|
583
|
+
f"<td{_col_class(idx, narrow_cols, path_cols)}>{_inline(c)}</td>"
|
|
583
584
|
for idx, c in enumerate(row)
|
|
584
585
|
)
|
|
585
586
|
+ "</tr>"
|
|
@@ -921,8 +922,30 @@ def _narrow_columns(
|
|
|
921
922
|
return narrow
|
|
922
923
|
|
|
923
924
|
|
|
924
|
-
def _col_class(col_idx: int, narrow_cols: set[int]) -> str:
|
|
925
|
-
|
|
925
|
+
def _col_class(col_idx: int, narrow_cols: set[int], path_cols: set[int]) -> str:
|
|
926
|
+
if col_idx in narrow_cols:
|
|
927
|
+
return ' class="td-narrow"'
|
|
928
|
+
if col_idx in path_cols:
|
|
929
|
+
return ' class="td-path"'
|
|
930
|
+
return ""
|
|
931
|
+
|
|
932
|
+
|
|
933
|
+
# A repo-relative path has no spaces, so `overflow-wrap: anywhere` puts its
|
|
934
|
+
# min-content width at one character and the auto table layout hands the
|
|
935
|
+
# column almost nothing while a prose neighbour takes the rest — the path
|
|
936
|
+
# then renders one or two characters per line. `td-path` floors the width.
|
|
937
|
+
_PATH_HEADER_PREFIXES: tuple[str, ...] = ("path", "파일 경로", "경로")
|
|
938
|
+
|
|
939
|
+
|
|
940
|
+
def _path_columns(header_cells: list[str]) -> set[int]:
|
|
941
|
+
return {
|
|
942
|
+
col
|
|
943
|
+
for col, header in enumerate(header_cells)
|
|
944
|
+
if _INLINE_MD_STRIP_RE.sub("", header or "")
|
|
945
|
+
.strip()
|
|
946
|
+
.lower()
|
|
947
|
+
.startswith(_PATH_HEADER_PREFIXES)
|
|
948
|
+
}
|
|
926
949
|
|
|
927
950
|
|
|
928
951
|
_DANGEROUS_URL_SCHEME_RE = re.compile(
|
|
@@ -960,6 +983,10 @@ def _inline(text: str) -> str:
|
|
|
960
983
|
# markdown source intentionally stacks short fields with <br>). html.escape
|
|
961
984
|
# above turned them into <br>; restore the tag.
|
|
962
985
|
out = out.replace("<br>", "<br>").replace("<br/>", "<br>").replace("<br />", "<br>")
|
|
986
|
+
# `<small>` demotes a cell's technical detail line below its plain-language
|
|
987
|
+
# first line (File Structure `details`). Allowlisted alongside <br>; every
|
|
988
|
+
# other tag stays escaped.
|
|
989
|
+
out = out.replace("<small>", "<small>").replace("</small>", "</small>")
|
|
963
990
|
return out
|
|
964
991
|
|
|
965
992
|
|