okstra 0.206.0 → 0.207.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 +3 -3
- package/dist/cli-registry.mjs +7 -1
- package/dist/cli-registry.mjs.map +1 -1
- package/dist/commands/lifecycle/install.mjs +1 -1
- package/dist/commands/lifecycle/install.mjs.map +1 -1
- package/docs/architecture/storage-model.md +1 -0
- package/docs/architecture.md +40 -16
- package/docs/cli.md +17 -15
- package/docs/contributor-change-matrix.md +3 -2
- package/docs/performance-improvement-plan-v2.md +1 -1
- package/docs/project-structure-overview.md +43 -20
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/operations/code-review.json +1 -1
- package/runtime/bin/lib/okstra/usage.sh +3 -3
- package/runtime/bin/okstra-compact-reminder.sh +1 -1
- package/runtime/bin/okstra-spawn-followups.py +2 -2
- package/runtime/prompts/duties/direction-selection-worker.json +1 -1
- package/runtime/prompts/launch.template.md +2 -2
- package/runtime/prompts/lead/adapters/cmux.md +4 -3
- package/runtime/prompts/lead/context-loader.md +1 -1
- package/runtime/prompts/lead/convergence.md +44 -12
- package/runtime/prompts/lead/okstra-lead-contract.md +44 -73
- package/runtime/prompts/lead/phase-routing.md +64 -0
- package/runtime/prompts/lead/report-writer.md +10 -8
- package/runtime/prompts/lead/team-contract.md +1 -1
- package/runtime/prompts/profiles/_clarification-recommendation.md +4 -4
- package/runtime/prompts/profiles/_coding-conventions-preflight.md +1 -1
- package/runtime/prompts/profiles/_common-contract.md +2 -2
- package/runtime/prompts/profiles/_coverage-critic.md +1 -1
- package/runtime/prompts/profiles/forbidden-actions.json +0 -94
- package/runtime/prompts/wizard/prompts.ko.json +2 -1
- package/runtime/python/okstra_ctl/adapters/hosts/antigravity/relay.md +1 -1
- package/runtime/python/okstra_ctl/adapters/hosts/claude-code/relay.md +5 -5
- package/runtime/python/okstra_ctl/adapters/hosts/codex/relay.md +1 -1
- package/runtime/python/okstra_ctl/adapters/hosts/external/relay.md +3 -2
- package/runtime/python/okstra_ctl/adapters/hosts/grok/relay.md +1 -1
- package/runtime/python/okstra_ctl/adapters/hosts/kimi/relay.md +1 -1
- package/runtime/python/okstra_ctl/adapters/providers/codex/adapter.py +17 -26
- package/runtime/python/okstra_ctl/agent/prompt_cli/batch.py +183 -0
- package/runtime/python/okstra_ctl/agent/prompt_cli/cli.py +60 -10
- package/runtime/python/okstra_ctl/agent/prompt_cli/corrections.py +1 -1
- package/runtime/python/okstra_ctl/agent/prompt_cli/jobs.py +21 -4
- package/runtime/python/okstra_ctl/agent/prompt_cli/materialize.py +10 -1
- package/runtime/python/okstra_ctl/analysis_inputs.py +0 -39
- package/runtime/python/okstra_ctl/analysis_scope.py +31 -0
- package/runtime/python/okstra_ctl/approval_decisions.py +32 -2
- package/runtime/python/okstra_ctl/asset_roots.py +19 -0
- package/runtime/python/okstra_ctl/assignment_resolver.py +8 -0
- package/runtime/python/okstra_ctl/blocking_checks.py +7 -0
- package/runtime/python/okstra_ctl/code_review_target.py +92 -6
- package/runtime/python/okstra_ctl/consumers.py +12 -0
- package/runtime/python/okstra_ctl/dispatch_checkpoints.py +121 -0
- package/runtime/python/okstra_ctl/dispatch_core.py +54 -32
- package/runtime/python/okstra_ctl/dispatch_state.py +34 -5
- package/runtime/python/okstra_ctl/doctor.py +2 -1
- package/runtime/python/okstra_ctl/domain/provider.py +5 -0
- package/runtime/python/okstra_ctl/domain/worker_presentation.py +21 -2
- package/runtime/python/okstra_ctl/domain/write_policy.py +2 -1
- package/runtime/python/okstra_ctl/execution_mutation_audit.py +46 -9
- package/runtime/python/okstra_ctl/handoff.py +11 -466
- package/runtime/python/okstra_ctl/handoff_error.py +5 -0
- package/runtime/python/okstra_ctl/implementation_direction.py +0 -477
- package/runtime/python/okstra_ctl/initial_prompt_materialization.py +13 -1
- package/runtime/python/okstra_ctl/lead_progress.py +33 -1
- package/runtime/python/okstra_ctl/manager_view.py +26 -19
- package/runtime/python/okstra_ctl/model_io/lines.py +21 -4
- package/runtime/python/okstra_ctl/models.py +4 -1
- package/runtime/python/okstra_ctl/operation_invocation.py +11 -2
- package/runtime/python/okstra_ctl/option_comparison.py +3 -165
- package/runtime/python/okstra_ctl/option_votes.py +3 -191
- package/runtime/python/okstra_ctl/paths.py +8 -6
- package/runtime/python/okstra_ctl/phases/catalog.py +56 -12
- package/runtime/python/okstra_ctl/phases/change_impact_analysis/boundary.json +11 -0
- package/runtime/python/okstra_ctl/phases/change_impact_analysis/entry.py +39 -0
- package/runtime/python/okstra_ctl/{report_html/view_models/change_impact_analysis.py → phases/change_impact_analysis/report.py} +3 -3
- package/runtime/python/okstra_ctl/phases/change_impact_analysis/spec.md +26 -0
- package/runtime/python/okstra_ctl/phases/change_impact_analysis/validation.py +23 -0
- package/runtime/python/okstra_ctl/phases/error_analysis/__init__.py +1 -0
- package/runtime/python/okstra_ctl/phases/error_analysis/boundary.json +9 -0
- package/runtime/{prompts/profiles/error-analysis.md → python/okstra_ctl/phases/error_analysis/profile.md} +2 -2
- package/runtime/python/okstra_ctl/{report_html/view_models/error_analysis.py → phases/error_analysis/report.py} +9 -8
- package/runtime/{templates/reports → python/okstra_ctl/phases/error_analysis/report_assets}/error-analysis-input.template.md +1 -1
- package/runtime/python/okstra_ctl/phases/error_analysis/spec.md +118 -0
- package/runtime/python/okstra_ctl/phases/error_analysis/validation.py +241 -0
- package/runtime/python/okstra_ctl/phases/feature_analysis/__init__.py +1 -0
- package/runtime/python/okstra_ctl/phases/feature_analysis/boundary.json +8 -0
- package/runtime/python/okstra_ctl/phases/feature_analysis/entry.py +63 -0
- package/runtime/python/okstra_ctl/{report_html/view_models/feature_analysis.py → phases/feature_analysis/report.py} +12 -5
- package/runtime/python/okstra_ctl/phases/feature_analysis/spec.md +22 -0
- package/runtime/python/okstra_ctl/phases/feature_analysis/validation.py +27 -0
- package/runtime/python/okstra_ctl/phases/feature_analysis/wizard.py +95 -0
- package/runtime/python/okstra_ctl/phases/final_verification/boundary.json +8 -0
- package/runtime/python/okstra_ctl/phases/final_verification/profile.md +2 -2
- package/runtime/{templates/reports → python/okstra_ctl/phases/final_verification/report_assets}/final-verification-input.template.md +1 -1
- package/runtime/python/okstra_ctl/phases/final_verification/spec.md +1 -1
- package/runtime/python/okstra_ctl/phases/implementation/__init__.py +1 -0
- package/runtime/python/okstra_ctl/phases/implementation/boundary.json +17 -0
- package/runtime/python/okstra_ctl/{implementation_stage.py → phases/implementation/entry.py} +22 -10
- package/runtime/{prompts/host-orchestration/implementation.md → python/okstra_ctl/phases/implementation/host-rules.md} +1 -1
- package/runtime/{prompts/profiles → python/okstra_ctl/phases/implementation/instructions}/_implementation-deliverable.md +1 -1
- package/runtime/{prompts/profiles → python/okstra_ctl/phases/implementation/instructions}/_implementation-executor.md +4 -3
- package/runtime/{prompts/profiles → python/okstra_ctl/phases/implementation/instructions}/_implementation-verifier.md +18 -7
- package/runtime/{prompts/profiles/implementation.md → python/okstra_ctl/phases/implementation/profile.md} +5 -5
- package/runtime/python/okstra_ctl/{report_html/view_models/implementation.py → phases/implementation/report.py} +3 -3
- package/runtime/{templates/reports → python/okstra_ctl/phases/implementation/report_assets}/implementation-input.template.md +1 -1
- package/runtime/python/okstra_ctl/phases/implementation/spec.md +238 -0
- package/runtime/python/okstra_ctl/phases/implementation/validation.py +205 -0
- package/runtime/python/okstra_ctl/phases/implementation/wizard.py +39 -0
- package/runtime/python/okstra_ctl/phases/implementation_option_selection/__init__.py +1 -0
- package/runtime/python/okstra_ctl/phases/implementation_option_selection/authoring.py +80 -0
- package/runtime/python/okstra_ctl/phases/implementation_option_selection/boundary.json +10 -0
- package/runtime/python/okstra_ctl/phases/implementation_option_selection/comparison.py +168 -0
- package/runtime/python/okstra_ctl/phases/implementation_option_selection/entry.py +27 -0
- package/runtime/{prompts/profiles/implementation-option-selection.md → python/okstra_ctl/phases/implementation_option_selection/profile.md} +3 -3
- package/runtime/python/okstra_ctl/{report_html/view_models/implementation_option_selection.py → phases/implementation_option_selection/report.py} +2 -2
- package/runtime/python/okstra_ctl/phases/implementation_option_selection/spec.md +83 -0
- package/runtime/python/okstra_ctl/{implementation_options.py → phases/implementation_option_selection/validation.py} +3 -3
- package/runtime/python/okstra_ctl/phases/implementation_option_selection/votes.py +194 -0
- package/runtime/python/okstra_ctl/phases/implementation_planning/__init__.py +1 -0
- package/runtime/python/okstra_ctl/phases/implementation_planning/authoring.py +2345 -0
- package/runtime/python/okstra_ctl/phases/implementation_planning/boundary.json +12 -0
- package/runtime/python/okstra_ctl/phases/implementation_planning/entry.py +161 -0
- package/runtime/python/okstra_ctl/phases/implementation_planning/guidance.py +178 -0
- package/runtime/{prompts/lead → python/okstra_ctl/phases/implementation_planning/instructions}/plan-body-verification.md +61 -51
- package/runtime/python/okstra_ctl/phases/implementation_planning/plan_body.py +3295 -0
- package/runtime/{prompts/profiles/implementation-planning.md → python/okstra_ctl/phases/implementation_planning/profile.md} +74 -25
- package/runtime/python/okstra_ctl/phases/implementation_planning/report.py +237 -0
- package/runtime/{templates/reports → python/okstra_ctl/phases/implementation_planning/report_assets}/implementation-planning-input.template.md +2 -2
- package/runtime/python/okstra_ctl/phases/implementation_planning/spec.md +204 -0
- package/runtime/python/okstra_ctl/phases/implementation_planning/validation.py +597 -0
- package/runtime/python/okstra_ctl/phases/implementation_planning/wizard.py +166 -0
- package/runtime/python/okstra_ctl/phases/improvement_discovery/boundary.json +12 -0
- package/runtime/python/okstra_ctl/{improvement_lenses.py → phases/improvement_discovery/lenses.py} +1 -6
- package/runtime/{prompts/profiles/improvement-discovery.md → python/okstra_ctl/phases/improvement_discovery/profile.md} +5 -5
- package/runtime/python/okstra_ctl/{report_html/view_models/improvement_discovery.py → phases/improvement_discovery/report.py} +3 -3
- package/runtime/{templates/reports → python/okstra_ctl/phases/improvement_discovery/report_assets}/improvement-discovery-input.template.md +1 -2
- package/runtime/python/okstra_ctl/phases/improvement_discovery/spec.md +29 -0
- package/runtime/{validators/validate_improvement_report.py → python/okstra_ctl/phases/improvement_discovery/validation.py} +5 -14
- package/runtime/python/okstra_ctl/phases/project_analysis/__init__.py +1 -0
- package/runtime/python/okstra_ctl/phases/project_analysis/boundary.json +8 -0
- package/runtime/python/okstra_ctl/phases/project_analysis/entry.py +11 -0
- package/runtime/python/okstra_ctl/{report_html/view_models/project_analysis.py → phases/project_analysis/report.py} +3 -3
- package/runtime/python/okstra_ctl/phases/project_analysis/spec.md +33 -0
- package/runtime/python/okstra_ctl/phases/project_analysis/validation.py +55 -0
- package/runtime/python/okstra_ctl/phases/release_handoff/__init__.py +1 -0
- package/runtime/python/okstra_ctl/phases/release_handoff/boundary.json +17 -0
- package/runtime/python/okstra_ctl/phases/release_handoff/entry.py +147 -0
- package/runtime/python/okstra_ctl/phases/release_handoff/operations.py +446 -0
- package/runtime/{prompts/profiles/release-handoff.md → python/okstra_ctl/phases/release_handoff/profile.md} +3 -3
- package/runtime/python/okstra_ctl/{report_html/view_models/release_handoff.py → phases/release_handoff/report.py} +3 -3
- package/runtime/{templates/reports → python/okstra_ctl/phases/release_handoff/report_assets}/release-handoff-input.template.md +1 -1
- package/runtime/python/okstra_ctl/phases/release_handoff/spec.md +233 -0
- package/runtime/python/okstra_ctl/phases/release_handoff/wizard.py +84 -0
- package/runtime/python/okstra_ctl/phases/requirements_discovery/__init__.py +1 -0
- package/runtime/python/okstra_ctl/phases/requirements_discovery/boundary.json +9 -0
- package/runtime/{prompts/profiles/requirements-discovery.md → python/okstra_ctl/phases/requirements_discovery/profile.md} +2 -3
- package/runtime/python/okstra_ctl/{report_html/view_models/requirements_discovery.py → phases/requirements_discovery/report.py} +3 -3
- package/runtime/python/okstra_ctl/phases/requirements_discovery/spec.md +132 -0
- package/runtime/{validators/validate_fanout.py → python/okstra_ctl/phases/requirements_discovery/validation.py} +11 -12
- package/runtime/python/okstra_ctl/phases/technical_verification/__init__.py +1 -0
- package/runtime/python/okstra_ctl/phases/technical_verification/boundary.json +9 -0
- package/runtime/python/okstra_ctl/phases/technical_verification/entry.py +100 -0
- package/runtime/{prompts/profiles/technical-verification.md → python/okstra_ctl/phases/technical_verification/profile.md} +2 -2
- package/runtime/python/okstra_ctl/{report_html/view_models/technical_verification.py → phases/technical_verification/report.py} +2 -2
- package/runtime/python/okstra_ctl/phases/technical_verification/spec.md +37 -0
- package/runtime/python/okstra_ctl/phases/technical_verification/validation.py +90 -0
- package/runtime/python/okstra_ctl/plan_approval.py +70 -0
- package/runtime/python/okstra_ctl/plan_items_cli.py +2 -2130
- package/runtime/python/okstra_ctl/process_group.py +118 -0
- package/runtime/python/okstra_ctl/profile_show.py +3 -3
- package/runtime/python/okstra_ctl/render.py +15 -4
- package/runtime/python/okstra_ctl/report_assembly.py +28 -92
- package/runtime/python/okstra_ctl/report_finalize.py +106 -2
- package/runtime/python/okstra_ctl/report_html/context_links.py +1 -1
- package/runtime/python/okstra_ctl/report_projections.py +1 -36
- package/runtime/python/okstra_ctl/report_routing.py +23 -0
- package/runtime/python/okstra_ctl/report_synthesis_packet.py +4 -73
- package/runtime/python/okstra_ctl/report_validation_identity.py +38 -0
- package/runtime/python/okstra_ctl/report_views.py +1 -1
- package/runtime/python/okstra_ctl/run.py +68 -350
- package/runtime/python/okstra_ctl/run_artifact_prune.py +200 -0
- package/runtime/python/okstra_ctl/stage_map.py +13 -0
- package/runtime/python/okstra_ctl/team.py +108 -9
- package/runtime/python/okstra_ctl/technical_verification_facts.py +52 -0
- package/runtime/python/okstra_ctl/wizard/__init__.py +31 -31
- package/runtime/python/okstra_ctl/wizard/api.py +18 -0
- package/runtime/python/okstra_ctl/wizard/outcome.py +3 -12
- package/runtime/python/okstra_ctl/wizard/registry.py +20 -12
- package/runtime/python/okstra_ctl/wizard/steps_analysis.py +0 -97
- package/runtime/python/okstra_ctl/wizard/steps_options.py +8 -0
- package/runtime/python/okstra_ctl/wizard/steps_plan.py +10 -263
- package/runtime/python/okstra_ctl/wizard/steps_roles.py +2 -1
- package/runtime/python/okstra_ctl/work_categories.py +1 -1
- package/runtime/python/okstra_ctl/worker_dispatch.py +44 -3
- package/runtime/python/okstra_ctl/worker_prompt_contract.py +36 -0
- package/runtime/python/okstra_ctl/worker_prompt_policy.py +19 -0
- package/runtime/python/okstra_ctl/worker_runner.py +21 -3
- package/runtime/python/okstra_ctl/workflow.py +26 -143
- package/runtime/python/okstra_ctl/write_policy.py +57 -7
- package/runtime/python/okstra_project/dirs.py +14 -0
- package/runtime/python/okstra_project/resolver.py +2 -1
- package/runtime/schemas/execution-manifest-v2.schema.json +2 -1
- package/runtime/skills/okstra-brief-gen/SKILL.md +3 -3
- package/runtime/skills/okstra-code-review/SKILL.md +70 -32
- package/runtime/skills/okstra-code-review/references/review-calibration.md +26 -6
- package/runtime/skills/okstra-run/SKILL.md +3 -3
- package/runtime/templates/manager/view.template.html +18 -1
- package/runtime/templates/reports/quick-input.template.md +1 -1
- package/runtime/templates/reports/task-brief.template.md +1 -1
- package/runtime/validators/validate-brief.py +2 -2
- package/runtime/validators/validate-run.py +299 -3940
- package/runtime/validators/validate_analysis_report.py +14 -126
- package/runtime/python/okstra_ctl/report_html/view_models/implementation_planning.py +0 -147
- package/runtime/python/okstra_ctl/technical_verification.py +0 -195
- /package/runtime/{prompts/profiles/change-impact-analysis.json → python/okstra_ctl/phases/change_impact_analysis/profile.json} +0 -0
- /package/runtime/{prompts/profiles/change-impact-analysis.md → python/okstra_ctl/phases/change_impact_analysis/profile.md} +0 -0
- /package/runtime/{templates/reports → python/okstra_ctl/phases/change_impact_analysis/report_assets}/change-impact-analysis-input.template.md +0 -0
- /package/runtime/{templates/reports/html/tasks → python/okstra_ctl/phases/change_impact_analysis/report_assets}/change-impact-analysis.template.html +0 -0
- /package/runtime/{templates/reports/md/tasks → python/okstra_ctl/phases/change_impact_analysis/report_assets}/change-impact-analysis.template.md +0 -0
- /package/runtime/{prompts/profiles/error-analysis.json → python/okstra_ctl/phases/error_analysis/profile.json} +0 -0
- /package/runtime/{templates/reports/html/tasks → python/okstra_ctl/phases/error_analysis/report_assets}/error-analysis.template.html +0 -0
- /package/runtime/{templates/reports/md/tasks → python/okstra_ctl/phases/error_analysis/report_assets}/error-analysis.template.md +0 -0
- /package/runtime/{prompts/profiles/feature-analysis.json → python/okstra_ctl/phases/feature_analysis/profile.json} +0 -0
- /package/runtime/{prompts/profiles/feature-analysis.md → python/okstra_ctl/phases/feature_analysis/profile.md} +0 -0
- /package/runtime/{templates/reports → python/okstra_ctl/phases/feature_analysis/report_assets}/feature-analysis-input.template.md +0 -0
- /package/runtime/{templates/reports/html/tasks → python/okstra_ctl/phases/feature_analysis/report_assets}/feature-analysis.template.html +0 -0
- /package/runtime/{templates/reports/md/tasks → python/okstra_ctl/phases/feature_analysis/report_assets}/feature-analysis.template.md +0 -0
- /package/runtime/{prompts/profiles → python/okstra_ctl/phases/implementation/instructions}/_implementation-diff-review.md +0 -0
- /package/runtime/{prompts/profiles → python/okstra_ctl/phases/implementation/instructions}/_implementation-self-check.md +0 -0
- /package/runtime/{prompts/profiles/implementation.json → python/okstra_ctl/phases/implementation/profile.json} +0 -0
- /package/runtime/{templates/reports/html/tasks → python/okstra_ctl/phases/implementation/report_assets}/implementation.template.html +0 -0
- /package/runtime/{templates/reports/md/tasks → python/okstra_ctl/phases/implementation/report_assets}/implementation.template.md +0 -0
- /package/runtime/{prompts/profiles/implementation-option-selection.json → python/okstra_ctl/phases/implementation_option_selection/profile.json} +0 -0
- /package/runtime/{templates/reports/html/tasks → python/okstra_ctl/phases/implementation_option_selection/report_assets}/implementation-option-selection.template.html +0 -0
- /package/runtime/{templates/reports/md/tasks → python/okstra_ctl/phases/implementation_option_selection/report_assets}/implementation-option-selection.template.md +0 -0
- /package/runtime/{prompts/host-orchestration/implementation-planning.md → python/okstra_ctl/phases/implementation_planning/host-rules.md} +0 -0
- /package/runtime/{prompts/profiles/implementation-planning.json → python/okstra_ctl/phases/implementation_planning/profile.json} +0 -0
- /package/runtime/{templates/reports/html/tasks → python/okstra_ctl/phases/implementation_planning/report_assets}/implementation-planning.template.html +0 -0
- /package/runtime/{templates/reports/md/tasks → python/okstra_ctl/phases/implementation_planning/report_assets}/implementation-planning.template.md +0 -0
- /package/runtime/{prompts/profiles/improvement-discovery.json → python/okstra_ctl/phases/improvement_discovery/profile.json} +0 -0
- /package/runtime/{templates/reports/html/tasks → python/okstra_ctl/phases/improvement_discovery/report_assets}/improvement-discovery.template.html +0 -0
- /package/runtime/{templates/reports/md/tasks → python/okstra_ctl/phases/improvement_discovery/report_assets}/improvement-discovery.template.md +0 -0
- /package/runtime/{prompts/profiles/project-analysis.json → python/okstra_ctl/phases/project_analysis/profile.json} +0 -0
- /package/runtime/{prompts/profiles/project-analysis.md → python/okstra_ctl/phases/project_analysis/profile.md} +0 -0
- /package/runtime/{templates/reports → python/okstra_ctl/phases/project_analysis/report_assets}/project-analysis-input.template.md +0 -0
- /package/runtime/{templates/reports/html/tasks → python/okstra_ctl/phases/project_analysis/report_assets}/project-analysis.template.html +0 -0
- /package/runtime/{templates/reports/md/tasks → python/okstra_ctl/phases/project_analysis/report_assets}/project-analysis.template.md +0 -0
- /package/runtime/{prompts/profiles/release-handoff.json → python/okstra_ctl/phases/release_handoff/profile.json} +0 -0
- /package/runtime/{templates/reports/html/tasks → python/okstra_ctl/phases/release_handoff/report_assets}/release-handoff.template.html +0 -0
- /package/runtime/{templates/reports/md/tasks → python/okstra_ctl/phases/release_handoff/report_assets}/release-handoff.template.md +0 -0
- /package/runtime/python/okstra_ctl/{fanout.py → phases/requirements_discovery/fanout.py} +0 -0
- /package/runtime/{prompts/profiles/requirements-discovery.json → python/okstra_ctl/phases/requirements_discovery/profile.json} +0 -0
- /package/runtime/{templates/reports → python/okstra_ctl/phases/requirements_discovery/report_assets}/fan-out-unit.template.md +0 -0
- /package/runtime/{templates/reports/html/tasks → python/okstra_ctl/phases/requirements_discovery/report_assets}/requirements-discovery.template.html +0 -0
- /package/runtime/{templates/reports/md/tasks → python/okstra_ctl/phases/requirements_discovery/report_assets}/requirements-discovery.template.md +0 -0
- /package/runtime/{prompts/profiles/technical-verification.json → python/okstra_ctl/phases/technical_verification/profile.json} +0 -0
- /package/runtime/{templates/reports/html/tasks → python/okstra_ctl/phases/technical_verification/report_assets}/technical-verification.template.html +0 -0
- /package/runtime/{templates/reports/md/tasks → python/okstra_ctl/phases/technical_verification/report_assets}/technical-verification.template.md +0 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Change impact analysis phase
|
|
2
|
+
|
|
3
|
+
## Purpose and boundaries
|
|
4
|
+
|
|
5
|
+
This phase maps a proposed change's preserved behavior, affected and unaffected boundaries, dependency propagation, tests and operational impact. It records planning constraints and open decisions without producing an execution plan. `profile.md` owns these analysis instructions and the adversarial convergence setting.
|
|
6
|
+
|
|
7
|
+
`entry.py` owns the feature-baseline selection and text-entry handlers. The common wizard registry preserves the existing question order: feature evidence before project evidence. Shared analysis-input code retains evidence ownership, review status, freshness, relation and path checks. The CLI evidence arguments and run artifacts are unchanged.
|
|
8
|
+
|
|
9
|
+
## Result and validation
|
|
10
|
+
|
|
11
|
+
`validation.py` owns the current-code evidence collection names and the rule limiting `planningInputs` to constraints and unknowns. `validators/validate_analysis_report.py` remains the common assembly point for phase semantics, current-code evidence, frozen input snapshots, worker completion and review-resolution checks. Evidence path predicates remain in `analysis_scope.py`.
|
|
12
|
+
|
|
13
|
+
`report.py` builds the impact graph and human view from `changeImpactAnalysis`. The input, Markdown and HTML templates live in `report_assets/`. Common schemas, analysis-review controls and report routing remain shared. The common lead routing policy chooses the next phase; the profile describes facts and constraints for that decision.
|
|
14
|
+
|
|
15
|
+
## Guarantees
|
|
16
|
+
|
|
17
|
+
| ID | Guarantee | Enforcement |
|
|
18
|
+
|---|---|---|
|
|
19
|
+
| CI-1 | Planning inputs contain constraints and unknowns only. | `scripts/okstra_ctl/phases/change_impact_analysis/validation.py::validate_change_impact_semantics` |
|
|
20
|
+
| CI-2 | Impact items require current-code evidence within the included scope. | `validators/validate_analysis_report.py::_validate_all_current_code_evidence`, `scripts/okstra_ctl/phases/change_impact_analysis/tests/test_change_impact_analysis_validation.py::test_change_impact_item_requires_current_code_evidence` |
|
|
21
|
+
| CI-3 | The wizard collects feature evidence before project evidence. | `tests/domain/wizard/test_analysis_inputs.py::test_change_impact_collects_feature_then_project_evidence` |
|
|
22
|
+
| CI-4 | Common validation checks the frozen analysis source, target and evidence snapshot. | `validators/validate_analysis_report.py::validate_analysis_snapshot` |
|
|
23
|
+
|
|
24
|
+
## Tests
|
|
25
|
+
|
|
26
|
+
The phase tests retain the existing impact-evidence and forbidden-planning-field assertions through the common report validator. Shared wizard tests cover selecting, skipping, reusing and editing evidence. Shared schema, report-assembly and rendering tests cover the existing report contract and views.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Change impact content boundaries; analysis evidence stays shared."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
EVIDENCE_COLLECTIONS = (
|
|
5
|
+
"preservedBehaviors",
|
|
6
|
+
"impactItems",
|
|
7
|
+
"dependencyBlastRadius",
|
|
8
|
+
"testImpact",
|
|
9
|
+
"operationalImpact",
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def validate_change_impact_semantics(data: dict, errors: list[str]) -> None:
|
|
14
|
+
analysis = data.get("changeImpactAnalysis") or {}
|
|
15
|
+
allowed = {"constraint", "unknown"}
|
|
16
|
+
for index, row in enumerate(analysis.get("planningInputs") or []):
|
|
17
|
+
if not isinstance(row, dict):
|
|
18
|
+
continue
|
|
19
|
+
for field in sorted(set(row) - allowed):
|
|
20
|
+
errors.append(
|
|
21
|
+
f"changeImpactAnalysis.planningInputs[{index}] forbids `{field}`; "
|
|
22
|
+
"planning inputs may contain constraints and unknowns only"
|
|
23
|
+
)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""오류 분석 단계의 진단 규칙과 자원."""
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"allowed": " - symptom and trigger evidence\n - root-cause hypotheses with supporting citations\n - reproduction gaps and missing observability\n - validation paths to confirm or refute hypotheses\n - one endStateCoverage row per brief end-state id (this phase authors no goal of its own)",
|
|
3
|
+
"forbidden": [
|
|
4
|
+
"source code edits, refactors, or fix attempts",
|
|
5
|
+
"implementation design or planning artifacts",
|
|
6
|
+
"executing builds, migrations, deployments, or any state-mutating command",
|
|
7
|
+
"starting `implementation-planning` or `implementation` inside this run (each must be a separate run, and `implementation` additionally requires an approved `implementation-planning` deliverable)"
|
|
8
|
+
]
|
|
9
|
+
}
|
|
@@ -25,10 +25,10 @@
|
|
|
25
25
|
- **Falsifiable cause candidates:** every root-cause candidate must include supporting evidence, the strongest falsifying evidence checked, confidence, and the next diagnostic action that would disprove it. A candidate that cannot be falsified is too vague for this phase.
|
|
26
26
|
- **Graph-aware scope:** a graph edge can explain ordering or duplication, but it is not proof of cause by itself. Cite code/log evidence before claiming an upstream related task caused the current symptom.
|
|
27
27
|
- **Sharp next diagnostic:** end with the single highest-value diagnostic command, log capture, or file inspection that should happen next, plus the expected signal that would confirm or reject the leading cause.
|
|
28
|
-
- **Fix-design boundary:** do not design the implementation fix beyond what is necessary to validate the cause.
|
|
28
|
+
- **Fix-design boundary:** do not design the implementation fix beyond what is necessary to validate the cause. Record whether the cause is credible and the next diagnostic. The common Phase Routing settles the next phase.
|
|
29
29
|
- Structured diagnosis and routing contract:
|
|
30
30
|
- `errorAnalysis` is the source of truth for reproduction status, `EA-NNN` cause candidates, the sharp next diagnostic, and the next route.
|
|
31
|
-
-
|
|
31
|
+
- Record the route selected by the lead through the common Phase Routing rules, its `routing.leadingCauseId`, and the matching direction.
|
|
32
32
|
- Structure is enforced by `schemas/final-report-v2.0.schema.json` `$defs.ErrorAnalysis`. `validators/validate-run.py::_validate_error_analysis_consistency` enforces the cross-field values: the routing target, a `routing.leadingCauseId` that names an actual `EA-NNN` candidate, the `verdictCard.direction` / `finalVerdict.direction` pair matching that target, and exactly one `phase-continuation` row in `followUpTasks` pointing at it. The prose of `direction` and `nextStep` is not scanned for routing wording.
|
|
33
33
|
- Primary focus areas:
|
|
34
34
|
- symptom and trigger clarification
|
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
"""Human-first error-analysis view model."""
|
|
2
|
+
|
|
2
3
|
from __future__ import annotations
|
|
3
4
|
|
|
4
5
|
import re
|
|
5
6
|
|
|
6
|
-
from
|
|
7
|
-
from
|
|
8
|
-
from
|
|
7
|
+
from okstra_ctl.report_html.common import evidence_index
|
|
8
|
+
from okstra_ctl.report_html.models import HumanReportView, VisualEdge, VisualNode
|
|
9
|
+
from okstra_ctl.report_html.visualizations import cause_graph_figure
|
|
9
10
|
|
|
10
11
|
# Record fields this template anchors as `id-<row id>` (see
|
|
11
12
|
# `HumanReportView.anchored_fields`); ids elsewhere land in the ledger.
|
|
12
|
-
ANCHORED_FIELDS = (
|
|
13
|
-
"errorAnalysis.causeCandidates",
|
|
14
|
-
)
|
|
13
|
+
ANCHORED_FIELDS = ("errorAnalysis.causeCandidates",)
|
|
15
14
|
|
|
16
15
|
|
|
17
16
|
_LEAD_CLAUSE = re.compile(r"\s[—–-]\s|(?<=[.。!?])\s")
|
|
@@ -43,7 +42,7 @@ def _cause_node(row: dict, leading_cause_id: str) -> VisualNode:
|
|
|
43
42
|
"candidate",
|
|
44
43
|
"leading" if leading else row["confidence"],
|
|
45
44
|
"",
|
|
46
|
-
note="Leading cause" if leading else f
|
|
45
|
+
note="Leading cause" if leading else f"Confidence {row['confidence']}",
|
|
47
46
|
)
|
|
48
47
|
|
|
49
48
|
|
|
@@ -79,7 +78,9 @@ def _cause_edges(rows: list[dict], symptom_id: str) -> tuple[VisualEdge, ...]:
|
|
|
79
78
|
|
|
80
79
|
|
|
81
80
|
def _cause_figure(error: dict):
|
|
82
|
-
symptom = VisualNode(
|
|
81
|
+
symptom = VisualNode(
|
|
82
|
+
"symptom", "Observed failure", "effect", "risk", "", note="Symptom"
|
|
83
|
+
)
|
|
83
84
|
rows = error.get("causeCandidates", [])
|
|
84
85
|
leading_cause_id = (error.get("routing") or {}).get("leadingCauseId") or ""
|
|
85
86
|
causes = tuple(_cause_node(row, leading_cause_id) for row in rows)
|
|
@@ -80,7 +80,7 @@ taskType: "{{FM_TASK_TYPE}}"
|
|
|
80
80
|
|
|
81
81
|
> Analysers MUST NOT expand the hypothesis space or evidence collection into items listed here. If an analyser believes an excluded item is causally linked to the symptom, it is reported as a recommended follow-up analysis task in the final report — never silently merged into the current root-cause narrative. If this section is left empty, analysers treat any cause beyond what `Symptom Summary` and `Reproduction Context` describe as out of scope by default.
|
|
82
82
|
|
|
83
|
-
**Enforced (delivery only):** `tests/contract/test_scope_boundary_delivery.py` pins that a filled section reaches every analysis worker through `okstra_ctl.analysis_packet` — drop it from `CANONICAL_BRIEF_SECTIONS` and every run's exclusions vanish silently. The exclusions themselves are prose and are **not** machine-checked: only the codebase-scan `out-of-scope` frontmatter is a path list, and `
|
|
83
|
+
**Enforced (delivery only):** `tests/contract/test_scope_boundary_delivery.py` pins that a filled section reaches every analysis worker through `okstra_ctl.analysis_packet` — drop it from `CANONICAL_BRIEF_SECTIONS` and every run's exclusions vanish silently. The exclusions themselves are prose and are **not** machine-checked: only the codebase-scan `out-of-scope` frontmatter is a path list, and `scripts/okstra_ctl/phases/improvement_discovery/validation.py` checks that one.
|
|
84
84
|
|
|
85
85
|
## Questions for Analysers
|
|
86
86
|
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# error-analysis process
|
|
2
|
+
|
|
3
|
+
## Guarantees
|
|
4
|
+
|
|
5
|
+
| ID | Guarantee | Enforcement |
|
|
6
|
+
|---|---|---|
|
|
7
|
+
| EA-1 | A blocked reproduction names its blocking reason; other statuses carry an empty reason | `scripts/okstra_ctl/phases/error_analysis/validation.py::_validate_reproduction` |
|
|
8
|
+
| EA-2 | Cause-chain links reference sibling candidates and reject self-links and cycles | `scripts/okstra_ctl/phases/error_analysis/tests/test_diagnosis.py::test_cause_chain_rejects_unresolvable_links` |
|
|
9
|
+
| EA-3 | A selected destination agrees with the cause reference, verdict directions, and continuation row | `scripts/okstra_ctl/phases/error_analysis/validation.py::_validate_error_analysis_consistency` |
|
|
10
|
+
| EA-4 | Common routing destinations match both supported report schemas | `scripts/okstra_ctl/phases/error_analysis/tests/test_diagnosis.py::test_error_routing_policy_matches_schema` |
|
|
11
|
+
|
|
12
|
+
## Index
|
|
13
|
+
|
|
14
|
+
- [1. Purpose](#1-purpose)
|
|
15
|
+
- [2. okstra-run wizard flow](#2-okstra-run-wizard-flow)
|
|
16
|
+
- [3. prepare_task_bundle handling](#3-prepare_task_bundle-handling)
|
|
17
|
+
- [4. lead execution flow](#4-lead-execution-flow)
|
|
18
|
+
- [5. Deliverables and prohibitions](#5-deliverables-and-prohibitions)
|
|
19
|
+
- [6. Code reviewed](#6-code-reviewed)
|
|
20
|
+
|
|
21
|
+
## 1. Purpose
|
|
22
|
+
|
|
23
|
+
`error-analysis` analyzes a reported error or incident to organize the symptom, trigger, root-cause candidate, reproduction gap, and validation path. It is not the phase that produces the fix itself or an implementation design.
|
|
24
|
+
|
|
25
|
+
## 2. okstra-run wizard flow
|
|
26
|
+
|
|
27
|
+
```mermaid
|
|
28
|
+
flowchart TD
|
|
29
|
+
Start[/okstra-run/] --> Common[common task identity flow]
|
|
30
|
+
Common --> Type[task-type = error-analysis]
|
|
31
|
+
Type --> Worktree{active worktree exists?}
|
|
32
|
+
Worktree -->|yes| RoleCount[role-count min..max<br/>omit uses recommended; skip if min==max]
|
|
33
|
+
Worktree -->|no| BaseRef[base-ref pick/text<br/>main recommended]
|
|
34
|
+
BaseRef --> RoleCount
|
|
35
|
+
RoleCount --> RoleModel[role-model provider/model per slot]
|
|
36
|
+
RoleModel --> RoleAdd[min=0 roles via role-add only<br/>default skip]
|
|
37
|
+
RoleAdd --> Extras[directive, related tasks, clarification]
|
|
38
|
+
Extras --> Confirm
|
|
39
|
+
Confirm --> Render[render-bundle --render-only]
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Launch selection uses role slots and model refs only: current-session lead is this session (listed on the confirmation summary), then each static role's count in `min..max` (default **recommended**; the count step is skipped when `min == max`), then one `provider/model` per slot. Roles with `min = 0` stay closed unless the user opens them with role-add (default skip). Duplicate model refs in the same role are rejected. There is no provider roster multi-pick and no `Use defaults / Customize` fork. Dynamic verifiers are not chosen at launch. `--workers` is a CLI compatibility input only, not a launch picker.
|
|
43
|
+
|
|
44
|
+
## 3. prepare_task_bundle handling
|
|
45
|
+
|
|
46
|
+
```mermaid
|
|
47
|
+
sequenceDiagram
|
|
48
|
+
participant Skill as okstra-run
|
|
49
|
+
participant Wizard as okstra_ctl.wizard
|
|
50
|
+
participant Run as prepare_task_bundle
|
|
51
|
+
participant WT as worktree/provision.py
|
|
52
|
+
participant Art as artifacts
|
|
53
|
+
|
|
54
|
+
Skill->>Wizard: task-type error-analysis selected
|
|
55
|
+
Wizard-->>Skill: workers/base-ref/model args
|
|
56
|
+
Skill->>Run: render-bundle --render-only
|
|
57
|
+
Run->>Run: canonical brief preflight
|
|
58
|
+
Run->>Run: validate brief/profile
|
|
59
|
+
Run->>Run: resolve worker roster
|
|
60
|
+
Run->>WT: provision/reuse worktree
|
|
61
|
+
Run->>Art: analysis-profile.md includes common contract
|
|
62
|
+
Run->>Art: task-manifest workflow next=validated report route
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
For canonical briefs, preflight runs before worker resolution, worktree provisioning, or report creation. A brief whose `reporter-confirmations` status is `pending` stops at this point; legacy briefs keep the compatibility path.
|
|
66
|
+
|
|
67
|
+
The final report records its next phase in `errorAnalysis.routing.nextTaskType`. The common Phase Routing guide selects the destination from the recorded cause evidence. When report validation passes, Phase 7 projects `workflow.nextRecommendedPhase` from that one field (`scripts/okstra_ctl/next_phase.py::project`) — a `ready` pointer naming it. A report that leaves the field empty leaves the pointer `pending`; there is no static fallback that supplies a phase the report did not author.
|
|
68
|
+
|
|
69
|
+
## 4. lead execution flow
|
|
70
|
+
|
|
71
|
+
```mermaid
|
|
72
|
+
flowchart TD
|
|
73
|
+
Intake[Phase 1 intake] --> Prompts[Phase 2 worker prompts]
|
|
74
|
+
Prompts --> Team[Phase 3 TeamCreate]
|
|
75
|
+
Team --> Dispatch[Phase 4/5 dispatch analysers]
|
|
76
|
+
Dispatch --> Evidence[worker outputs<br/>root-cause hypotheses]
|
|
77
|
+
Evidence --> Conv[Phase 5.5 convergence<br/>default maxRounds = 2]
|
|
78
|
+
Conv --> Report[Phase 6 report-writer final report]
|
|
79
|
+
Report --> Persist[Phase 7 persist + validate]
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
The workers analyze the symptom and evidence independently. A finding two distinct role executions derived on their own is recorded as full consensus at Round 0 in both modes — independent co-derivation is already cross-verification, so the adversarial burden of proof applies to single-source claims. Evidence-backed counter-evidence classifies a finding `contested` in the round it lands and takes it out of the verification queue; later agreement can neither erase it nor cost another round. The report-writer does not analyze during Phase 4/5 but writes the final report in Phase 6.
|
|
83
|
+
|
|
84
|
+
## 5. Deliverables and prohibitions
|
|
85
|
+
|
|
86
|
+
```mermaid
|
|
87
|
+
flowchart LR
|
|
88
|
+
Symptom[Symptom] --> Hyp[Root-cause candidates]
|
|
89
|
+
Hyp --> Gap[Reproduction gaps]
|
|
90
|
+
Gap --> Validate[Validation path]
|
|
91
|
+
Validate --> Next[Recommended next diagnostic/planning step]
|
|
92
|
+
Hyp -. forbidden .-> Fix[Code fix in this run]
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
The expected final-report content is:
|
|
96
|
+
|
|
97
|
+
- evidence-backed cause analysis
|
|
98
|
+
- uncertainty boundary
|
|
99
|
+
- practical next diagnostic steps
|
|
100
|
+
- if there is blocking uncertainty, `## 1. Clarification Items`, usually `Blocks=next-phase`
|
|
101
|
+
|
|
102
|
+
For `error-analysis`, the structured `errorAnalysis` object is the source of truth for the verbatim symptom, reproduction status, `EA-NNN` cause candidates and their counter-evidence, the next diagnostic, and routing. Its shape is enforced by the final-report schema; `validators/validate-run.py::_validate_error_analysis_consistency` enforces the cross-field values — the routing target, a `leadingCauseId` naming a real cause candidate, the two `direction` fields agreeing with that target, and the single matching `phase-continuation` follow-up row. The common Phase Routing guide owns the conditions for selecting the next phase.
|
|
103
|
+
|
|
104
|
+
What is prohibited is source edit, refactor, fix attempt, implementation design artifact, and running build/migration/deploy. Deferring ambiguity that could be answered from code or logs to a user question is also a defect per the profile.
|
|
105
|
+
|
|
106
|
+
## 6. Code reviewed
|
|
107
|
+
|
|
108
|
+
- [`prompts/profiles/error-analysis.md`](profile.md)
|
|
109
|
+
- [`templates/reports/error-analysis-input.template.md`](report_assets/error-analysis-input.template.md)
|
|
110
|
+
- [`scripts/okstra_ctl/workflow.py`](../../workflow.py)
|
|
111
|
+
- [`scripts/okstra_ctl/wizard/`](../../wizard)
|
|
112
|
+
- [`prompts/lead/okstra-lead-contract.md`](../../../../prompts/lead/okstra-lead-contract.md)
|
|
113
|
+
|
|
114
|
+
## Phase ownership
|
|
115
|
+
|
|
116
|
+
`profile.md` and `profile.json` own the diagnostic procedure and worker requirements. `validation.py` owns reproduction, cause-chain, and authored routing consistency checks. `report.py` and `report_assets/` own the diagnosis display and input template. The common lead guide `prompts/lead/phase-routing.md` owns destination selection; common evidence reading, clarification contracts, and lifecycle projection remain outside this phase. The shared 2.0 and 3.0 final-report schemas retain `$defs.ErrorAnalysis`.
|
|
117
|
+
|
|
118
|
+
`tests/test_diagnosis.py` exercises diagnosis rules. The common contract suite retains workflow projection and final-report assembly tests. No additional entry or wizard module is needed: this phase uses the common brief preflight and worker-selection flow.
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
"""오류 분석의 재현·원인 연결·선택된 이동 결과를 검증한다."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, Mapping
|
|
6
|
+
from okstra_ctl.workflow import ERROR_ANALYSIS_ROUTING_DIRECTIONS
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _upstream_by_candidate(candidates: list[Any]) -> dict[str, list[str]]:
|
|
10
|
+
upstream: dict[str, list[str]] = {}
|
|
11
|
+
for candidate in candidates:
|
|
12
|
+
if not isinstance(candidate, Mapping):
|
|
13
|
+
continue
|
|
14
|
+
candidate_id = candidate.get("id")
|
|
15
|
+
declared = candidate.get("downstreamOf")
|
|
16
|
+
if isinstance(candidate_id, str) and isinstance(declared, list):
|
|
17
|
+
upstream[candidate_id] = [row for row in declared if isinstance(row, str)]
|
|
18
|
+
return upstream
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _chain_cycle(upstream: dict[str, list[str]]) -> list[str]:
|
|
22
|
+
"""The first cycle reachable through `downstreamOf`, as the ids that form it.
|
|
23
|
+
|
|
24
|
+
A cycle is a diagnosis that says each step is caused by the next, so it
|
|
25
|
+
names no first cause. It also hangs the figure's layering, which relaxes
|
|
26
|
+
until depths settle.
|
|
27
|
+
"""
|
|
28
|
+
settled: set[str] = set()
|
|
29
|
+
for start in sorted(upstream):
|
|
30
|
+
stack = [(start, False)]
|
|
31
|
+
on_path: list[str] = []
|
|
32
|
+
active: dict[str, int] = {}
|
|
33
|
+
while stack:
|
|
34
|
+
current, exiting = stack.pop()
|
|
35
|
+
if exiting:
|
|
36
|
+
active.pop(current)
|
|
37
|
+
on_path.pop()
|
|
38
|
+
settled.add(current)
|
|
39
|
+
continue
|
|
40
|
+
if current in active:
|
|
41
|
+
return on_path[active[current] :] + [current]
|
|
42
|
+
if current in settled or current not in upstream:
|
|
43
|
+
continue
|
|
44
|
+
active[current] = len(on_path)
|
|
45
|
+
on_path.append(current)
|
|
46
|
+
stack.append((current, True))
|
|
47
|
+
stack.extend((parent, False) for parent in upstream[current])
|
|
48
|
+
return []
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _validate_cause_chain(
|
|
52
|
+
candidates: list[Any], candidate_ids: set[str], failures: list[str]
|
|
53
|
+
) -> None:
|
|
54
|
+
"""`downstreamOf` must name a sibling candidate, and never itself."""
|
|
55
|
+
upstream = _upstream_by_candidate(candidates)
|
|
56
|
+
for candidate_id in sorted(upstream):
|
|
57
|
+
unknown = sorted(set(upstream[candidate_id]) - candidate_ids)
|
|
58
|
+
if unknown:
|
|
59
|
+
failures.append(
|
|
60
|
+
f"final-report data.json: {candidate_id}.downstreamOf names "
|
|
61
|
+
"unknown cause candidate(s): " + ", ".join(unknown) + "."
|
|
62
|
+
)
|
|
63
|
+
if candidate_id in upstream[candidate_id]:
|
|
64
|
+
failures.append(
|
|
65
|
+
f"final-report data.json: {candidate_id}.downstreamOf names itself."
|
|
66
|
+
)
|
|
67
|
+
cycle = _chain_cycle(
|
|
68
|
+
{
|
|
69
|
+
key: [row for row in value if row in candidate_ids]
|
|
70
|
+
for key, value in upstream.items()
|
|
71
|
+
}
|
|
72
|
+
)
|
|
73
|
+
if cycle:
|
|
74
|
+
failures.append(
|
|
75
|
+
"final-report data.json: cause candidates form a downstreamOf cycle: "
|
|
76
|
+
+ " -> ".join(cycle)
|
|
77
|
+
+ "."
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _validate_reproduction(
|
|
82
|
+
error_analysis: Mapping[str, Any], failures: list[str]
|
|
83
|
+
) -> None:
|
|
84
|
+
reproduction_value = error_analysis.get("reproduction")
|
|
85
|
+
reproduction = reproduction_value if isinstance(reproduction_value, Mapping) else {}
|
|
86
|
+
reproduction_status = reproduction.get("status")
|
|
87
|
+
blocked_reason = reproduction.get("blockedReason")
|
|
88
|
+
if reproduction_status == "blocked-before-repro":
|
|
89
|
+
if not isinstance(blocked_reason, str) or not blocked_reason.strip():
|
|
90
|
+
failures.append(
|
|
91
|
+
"final-report data.json: blocked-before-repro requires a non-empty "
|
|
92
|
+
"errorAnalysis.reproduction.blockedReason."
|
|
93
|
+
)
|
|
94
|
+
elif blocked_reason != "":
|
|
95
|
+
failures.append(
|
|
96
|
+
"final-report data.json: errorAnalysis.reproduction.blockedReason "
|
|
97
|
+
"must be exactly empty unless status is blocked-before-repro."
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _validate_cause_candidates(candidates: list[Any], failures: list[str]) -> set[str]:
|
|
102
|
+
candidate_ids: list[str] = []
|
|
103
|
+
for candidate in candidates:
|
|
104
|
+
if not isinstance(candidate, Mapping):
|
|
105
|
+
continue
|
|
106
|
+
candidate_id = candidate.get("id")
|
|
107
|
+
if isinstance(candidate_id, str):
|
|
108
|
+
candidate_ids.append(candidate_id)
|
|
109
|
+
duplicate_ids = sorted(
|
|
110
|
+
candidate_id
|
|
111
|
+
for candidate_id in set(candidate_ids)
|
|
112
|
+
if candidate_ids.count(candidate_id) > 1
|
|
113
|
+
)
|
|
114
|
+
if duplicate_ids:
|
|
115
|
+
failures.append(
|
|
116
|
+
"final-report data.json: duplicate cause candidate id(s): "
|
|
117
|
+
+ ", ".join(duplicate_ids)
|
|
118
|
+
+ "."
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
_validate_cause_chain(candidates, set(candidate_ids), failures)
|
|
122
|
+
|
|
123
|
+
return set(candidate_ids)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _validate_routing_cause(
|
|
127
|
+
error_analysis: Mapping[str, Any],
|
|
128
|
+
candidates: list[Any],
|
|
129
|
+
candidate_id_set: set[str],
|
|
130
|
+
failures: list[str],
|
|
131
|
+
) -> Any:
|
|
132
|
+
routing_value = error_analysis.get("routing")
|
|
133
|
+
routing = routing_value if isinstance(routing_value, Mapping) else {}
|
|
134
|
+
target = routing.get("nextTaskType")
|
|
135
|
+
leading_cause_id = routing.get("leadingCauseId")
|
|
136
|
+
if isinstance(target, str) and target not in ERROR_ANALYSIS_ROUTING_DIRECTIONS:
|
|
137
|
+
failures.append(
|
|
138
|
+
"final-report data.json: errorAnalysis.routing has unsupported "
|
|
139
|
+
f"routing target `{target}`."
|
|
140
|
+
)
|
|
141
|
+
if target == "implementation-option-selection":
|
|
142
|
+
if not candidates:
|
|
143
|
+
failures.append(
|
|
144
|
+
"final-report data.json: implementation-option-selection routing requires "
|
|
145
|
+
"at least one cause candidate."
|
|
146
|
+
)
|
|
147
|
+
if (
|
|
148
|
+
not isinstance(leading_cause_id, str)
|
|
149
|
+
or leading_cause_id not in candidate_id_set
|
|
150
|
+
):
|
|
151
|
+
failures.append(
|
|
152
|
+
"final-report data.json: implementation-option-selection routing "
|
|
153
|
+
"leadingCauseId must reference a cause candidate."
|
|
154
|
+
)
|
|
155
|
+
elif target == "error-analysis" and (
|
|
156
|
+
not isinstance(leading_cause_id, str)
|
|
157
|
+
or (leading_cause_id != "" and leading_cause_id not in candidate_id_set)
|
|
158
|
+
):
|
|
159
|
+
failures.append(
|
|
160
|
+
"final-report data.json: error-analysis routing leadingCauseId must be "
|
|
161
|
+
"empty or reference a cause candidate."
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
return target
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def _validate_verdict_directions(
|
|
168
|
+
data: Mapping[str, Any], target: Any, failures: list[str]
|
|
169
|
+
) -> None:
|
|
170
|
+
expected_direction = ERROR_ANALYSIS_ROUTING_DIRECTIONS.get(target)
|
|
171
|
+
verdict_card_value = data.get("verdictCard")
|
|
172
|
+
verdict_card = verdict_card_value if isinstance(verdict_card_value, Mapping) else {}
|
|
173
|
+
final_verdict_value = data.get("finalVerdict")
|
|
174
|
+
final_verdict = (
|
|
175
|
+
final_verdict_value if isinstance(final_verdict_value, Mapping) else {}
|
|
176
|
+
)
|
|
177
|
+
if expected_direction:
|
|
178
|
+
for field_name, verdict in (
|
|
179
|
+
("verdictCard", verdict_card),
|
|
180
|
+
("finalVerdict", final_verdict),
|
|
181
|
+
):
|
|
182
|
+
if verdict.get("direction") != expected_direction:
|
|
183
|
+
failures.append(
|
|
184
|
+
f"final-report data.json: {field_name}.direction must be "
|
|
185
|
+
f"`{expected_direction}` for `{target}` routing."
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def _validate_phase_continuation(
|
|
190
|
+
data: Mapping[str, Any], target: Any, failures: list[str]
|
|
191
|
+
) -> None:
|
|
192
|
+
follow_up_tasks_value = data.get("followUpTasks")
|
|
193
|
+
follow_up_tasks = (
|
|
194
|
+
follow_up_tasks_value if isinstance(follow_up_tasks_value, list) else []
|
|
195
|
+
)
|
|
196
|
+
continuations = [
|
|
197
|
+
row
|
|
198
|
+
for row in follow_up_tasks
|
|
199
|
+
if isinstance(row, Mapping) and row.get("origin") == "phase-continuation"
|
|
200
|
+
]
|
|
201
|
+
if len(continuations) != 1:
|
|
202
|
+
failures.append(
|
|
203
|
+
"final-report data.json: followUpTasks must contain exactly one "
|
|
204
|
+
"phase-continuation row."
|
|
205
|
+
)
|
|
206
|
+
else:
|
|
207
|
+
continuation = continuations[0]
|
|
208
|
+
if continuation.get("suggestedTaskType") != target:
|
|
209
|
+
failures.append(
|
|
210
|
+
"final-report data.json: phase-continuation suggestedTaskType "
|
|
211
|
+
"must match the routing target."
|
|
212
|
+
)
|
|
213
|
+
# `newTaskId` 의 형식과 `priority` 값은 여기서 보지 않는다. 이 행은
|
|
214
|
+
# `okstra-spawn-followups.py` 의 `NON_SPAWNING_ORIGINS` 에 들어 있어
|
|
215
|
+
# 아무것도 스폰하지 않는 표식이고, 그래서 두 필드에는 소비자가 없다.
|
|
216
|
+
# 게다가 그 스크립트의 priority 기본값은 `P1` 인데 여기서는 `P0` 를
|
|
217
|
+
# 요구해 두 값이 정면으로 어긋났고, 두 규칙 중 어느 쪽도 report-writer
|
|
218
|
+
# 가 읽는 스키마·프로필·템플릿 어디에도 적혀 있지 않았다 — 작성자가
|
|
219
|
+
# 알 수 없는 규칙을 소비자 없는 필드에 걸어 두고 있었다.
|
|
220
|
+
if continuation.get("autoSpawn") != "no":
|
|
221
|
+
failures.append(
|
|
222
|
+
"final-report data.json: phase-continuation autoSpawn must be no."
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def _validate_error_analysis_consistency(
|
|
227
|
+
data: Mapping[str, Any], failures: list[str]
|
|
228
|
+
) -> None:
|
|
229
|
+
error_analysis_value = data.get("errorAnalysis")
|
|
230
|
+
error_analysis = (
|
|
231
|
+
error_analysis_value if isinstance(error_analysis_value, Mapping) else {}
|
|
232
|
+
)
|
|
233
|
+
_validate_reproduction(error_analysis, failures)
|
|
234
|
+
candidates_value = error_analysis.get("causeCandidates")
|
|
235
|
+
candidates = candidates_value if isinstance(candidates_value, list) else []
|
|
236
|
+
candidate_ids = _validate_cause_candidates(candidates, failures)
|
|
237
|
+
target = _validate_routing_cause(
|
|
238
|
+
error_analysis, candidates, candidate_ids, failures
|
|
239
|
+
)
|
|
240
|
+
_validate_verdict_directions(data, target, failures)
|
|
241
|
+
_validate_phase_continuation(data, target, failures)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""기능 분석 단계의 대상 해석과 보고서 정책."""
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
{
|
|
2
|
+
"allowed": " - confirmed target and normal, alternate, and failure flows\n - rules, state transitions, external calls, and test coverage scope\n",
|
|
3
|
+
"forbidden": [
|
|
4
|
+
"source or configuration edits",
|
|
5
|
+
"tests, builds, migrations, or deployments",
|
|
6
|
+
"starting any other lifecycle phase inside this run"
|
|
7
|
+
]
|
|
8
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""선택한 프로젝트 증거에서 기능 분석 대상을 해석한다."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
import re
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Mapping, Sequence
|
|
7
|
+
from okstra_ctl.prepare_error import PrepareError
|
|
8
|
+
from okstra_ctl.analysis_inputs import (
|
|
9
|
+
AnalysisInputError,
|
|
10
|
+
AnalysisReportCandidate,
|
|
11
|
+
ResolvedEvidenceInput,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
_FEATURE_ID_RE = re.compile(r"PF-\d{3}")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def resolve_analysis_target(
|
|
18
|
+
raw_target: str,
|
|
19
|
+
evidence_inputs: Sequence[ResolvedEvidenceInput],
|
|
20
|
+
candidates: Mapping[Path, AnalysisReportCandidate],
|
|
21
|
+
) -> dict[str, object]:
|
|
22
|
+
"""Normalize a feature-index identifier or a non-empty free-text target."""
|
|
23
|
+
target = raw_target.strip()
|
|
24
|
+
if not target:
|
|
25
|
+
raise AnalysisInputError("analysis target must not be empty")
|
|
26
|
+
if not _FEATURE_ID_RE.fullmatch(target):
|
|
27
|
+
return {"inputMode": "free-text", "requestedValue": target}
|
|
28
|
+
matches: list[dict[object, object]] = []
|
|
29
|
+
for evidence in evidence_inputs:
|
|
30
|
+
if evidence.relation != "project-context":
|
|
31
|
+
continue
|
|
32
|
+
candidate = candidates.get(evidence.report_path)
|
|
33
|
+
if candidate is None:
|
|
34
|
+
candidate = next(
|
|
35
|
+
(
|
|
36
|
+
value
|
|
37
|
+
for path, value in candidates.items()
|
|
38
|
+
if path.resolve() == evidence.report_path
|
|
39
|
+
),
|
|
40
|
+
None,
|
|
41
|
+
)
|
|
42
|
+
if candidate is None:
|
|
43
|
+
continue
|
|
44
|
+
matches.extend(
|
|
45
|
+
feature
|
|
46
|
+
for feature in candidate.feature_index
|
|
47
|
+
if isinstance(feature, dict) and feature.get("id") == target
|
|
48
|
+
)
|
|
49
|
+
if len(matches) != 1:
|
|
50
|
+
raise AnalysisInputError(
|
|
51
|
+
f"exactly one project-context feature must match {target}"
|
|
52
|
+
)
|
|
53
|
+
return {
|
|
54
|
+
"inputMode": "feature-index",
|
|
55
|
+
"requestedValue": target,
|
|
56
|
+
"feature": dict(matches[0]),
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def validate_feature_target_input(raw_target: str) -> None:
|
|
61
|
+
"""실행 생성 전에 필수 대상 입력을 확인한다."""
|
|
62
|
+
if not raw_target.strip():
|
|
63
|
+
raise PrepareError("feature-analysis requires --analysis-target")
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
"""Human-first feature-analysis view model."""
|
|
2
|
+
|
|
2
3
|
from __future__ import annotations
|
|
3
4
|
|
|
4
|
-
from
|
|
5
|
-
from
|
|
6
|
-
from
|
|
5
|
+
from okstra_ctl.report_html.common import analysis_review_ids, evidence_index
|
|
6
|
+
from okstra_ctl.report_html.models import HumanReportView, VisualNode
|
|
7
|
+
from okstra_ctl.report_html.visualizations import flow_figure
|
|
7
8
|
|
|
8
9
|
# Record fields this template anchors as `id-<row id>` (see
|
|
9
10
|
# `HumanReportView.anchored_fields`); ids elsewhere land in the ledger.
|
|
@@ -15,7 +16,11 @@ ANCHORED_FIELDS = (
|
|
|
15
16
|
)
|
|
16
17
|
|
|
17
18
|
|
|
18
|
-
_FLOW_KINDS = {
|
|
19
|
+
_FLOW_KINDS = {
|
|
20
|
+
"normal": "Normal path",
|
|
21
|
+
"alternative": "Alternative path",
|
|
22
|
+
"failure": "Failure path",
|
|
23
|
+
}
|
|
19
24
|
|
|
20
25
|
|
|
21
26
|
def _flow_nodes(feature: dict) -> tuple[VisualNode, ...]:
|
|
@@ -33,7 +38,9 @@ def _flow_nodes(feature: dict) -> tuple[VisualNode, ...]:
|
|
|
33
38
|
|
|
34
39
|
def build_feature_analysis_view(data: dict) -> HumanReportView:
|
|
35
40
|
feature = data["featureAnalysis"]
|
|
36
|
-
figure = flow_figure(
|
|
41
|
+
figure = flow_figure(
|
|
42
|
+
nodes=_flow_nodes(feature), edges=(), title="Feature behavior paths"
|
|
43
|
+
)
|
|
37
44
|
context = {
|
|
38
45
|
"humanSummary": data["humanSummary"],
|
|
39
46
|
"feature": feature,
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Feature analysis phase
|
|
2
|
+
|
|
3
|
+
## Purpose and boundaries
|
|
4
|
+
|
|
5
|
+
This phase describes a confirmed feature's normal, alternate and failure flows, domain rules, state changes, external interactions and test coverage. The profile guides read-only analysis without implementation alternatives or execution plans. Evidence report loading, user review authority, source snapshots and report publication remain common contracts.
|
|
6
|
+
|
|
7
|
+
## Inputs and questions
|
|
8
|
+
|
|
9
|
+
`entry.resolve_analysis_target` trims free-text targets and resolves `PF-` identifiers against exactly one feature in the selected project-context evidence. `entry.validate_feature_target_input` rejects a missing target before provisioning. The wizard preserves project-evidence selection before feature-index selection or direct target text, including reuse and rewind behavior. `wizard.py` owns target questions; shared evidence questions stay in `wizard/steps_analysis.py`.
|
|
10
|
+
|
|
11
|
+
## Reports and validation
|
|
12
|
+
|
|
13
|
+
`profile.md` and `profile.json` own instructions and worker roles. `report.py` and `report_assets/` own feature report content. `validation._validate_feature_semantics` checks that report target fields match the resolved target. The common analysis validator continues to enforce current-code evidence, included paths, worker completion and scope confirmation. Shared schemas retain the same versions and artifact names. The profile does not choose a next lifecycle phase; orchestration remains common.
|
|
14
|
+
|
|
15
|
+
## Guarantees
|
|
16
|
+
|
|
17
|
+
| ID | Guarantee | Enforcement |
|
|
18
|
+
|---|---|---|
|
|
19
|
+
| FA-1 | A feature-index target resolves to exactly one selected project-context feature; free text cannot be empty. | `scripts/okstra_ctl/phases/feature_analysis/entry.py::resolve_analysis_target` |
|
|
20
|
+
| FA-2 | Report target identity matches the resolved analysis target. | `scripts/okstra_ctl/phases/feature_analysis/validation.py::_validate_feature_semantics` |
|
|
21
|
+
| FA-3 | Current-code evidence remains within the declared analysis scope. | `validators/validate_analysis_report.py::_validate_all_current_code_evidence` |
|
|
22
|
+
| FA-4 | Feature target selection preserves the wizard's rendered arguments. | `tests/domain/wizard/test_analysis_inputs.py::test_feature_target_from_index_reaches_render_args` |
|