okstra 0.205.1 → 0.206.1
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 +2 -2
- package/dist/commands/lifecycle/install.mjs +2 -1
- package/dist/commands/lifecycle/install.mjs.map +1 -1
- package/docs/architecture.md +16 -16
- package/docs/cli.md +4 -4
- package/docs/contributor-change-matrix.md +3 -2
- package/docs/performance-improvement-plan-v2.md +1 -1
- package/docs/project-structure-overview.md +45 -21
- package/package.json +2 -3
- package/runtime/BUILD.json +2 -2
- package/runtime/bin/okstra-spawn-followups.py +2 -2
- package/runtime/prompts/launch.template.md +1 -1
- package/runtime/prompts/lead/context-loader.md +1 -1
- package/runtime/prompts/lead/convergence.md +3 -3
- package/runtime/prompts/lead/okstra-lead-contract.md +14 -55
- package/runtime/prompts/lead/phase-routing.md +82 -0
- package/runtime/prompts/lead/report-writer.md +2 -2
- package/runtime/prompts/lead/team-contract.md +1 -1
- package/runtime/prompts/profiles/_coding-conventions-preflight.md +1 -1
- package/runtime/prompts/profiles/_common-contract.md +1 -1
- package/runtime/prompts/profiles/_coverage-critic.md +1 -1
- package/runtime/prompts/profiles/forbidden-actions.json +0 -94
- package/runtime/python/okstra_ctl/agent/prompt_cli/corrections.py +1 -1
- 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/asset_roots.py +19 -0
- package/runtime/python/okstra_ctl/consumers.py +12 -0
- package/runtime/python/okstra_ctl/contract_graph.py +75 -10
- package/runtime/python/okstra_ctl/dispatch_state.py +22 -0
- package/runtime/python/okstra_ctl/doctor.py +15 -4
- package/runtime/python/okstra_ctl/execution_mutation_audit.py +27 -1
- 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 +9 -485
- package/runtime/python/okstra_ctl/initial_prompt_materialization.py +8 -1
- package/runtime/python/okstra_ctl/next_phase.py +2 -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 +22 -6
- package/runtime/python/okstra_ctl/phases/__init__.py +4 -0
- package/runtime/python/okstra_ctl/phases/catalog.py +260 -0
- 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/{docs/task-process/error-analysis.md → runtime/python/okstra_ctl/phases/error_analysis/spec.md} +22 -7
- 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/__init__.py +4 -0
- package/runtime/python/okstra_ctl/phases/final_verification/boundary.json +8 -0
- package/runtime/python/okstra_ctl/phases/final_verification/entry.py +166 -0
- package/runtime/{prompts/profiles/final-verification.md → python/okstra_ctl/phases/final_verification/profile.md} +5 -5
- package/runtime/python/okstra_ctl/{report_html/view_models/final_verification.py → phases/final_verification/report.py} +12 -3
- package/runtime/{templates/reports → python/okstra_ctl/phases/final_verification/report_assets}/final-verification-input.template.md +1 -1
- package/{docs/task-process/final-verification.md → runtime/python/okstra_ctl/phases/final_verification/spec.md} +42 -25
- package/runtime/python/okstra_ctl/phases/final_verification/target.py +296 -0
- package/runtime/python/okstra_ctl/phases/final_verification/validation.py +190 -0
- package/runtime/python/okstra_ctl/phases/final_verification/wizard.py +38 -0
- 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 +4 -4
- 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/{docs/task-process/implementation.md → runtime/python/okstra_ctl/phases/implementation/spec.md} +20 -8
- 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} +2 -2
- package/runtime/python/okstra_ctl/{report_html/view_models/implementation_option_selection.py → phases/implementation_option_selection/report.py} +2 -2
- package/{docs/task-process/implementation-option-selection.md → runtime/python/okstra_ctl/phases/implementation_option_selection/spec.md} +20 -7
- 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/{docs/task-process/implementation-planning.md → runtime/python/okstra_ctl/phases/implementation_planning/spec.md} +30 -6
- 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/{docs/task-process/release-handoff.md → runtime/python/okstra_ctl/phases/release_handoff/spec.md} +22 -9
- 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/{docs/task-process/requirements-discovery.md → runtime/python/okstra_ctl/phases/requirements_discovery/spec.md} +25 -6
- 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} +1 -1
- 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/profile_show.py +9 -3
- package/runtime/python/okstra_ctl/render.py +9 -2
- package/runtime/python/okstra_ctl/render_final_report.py +3 -2
- package/runtime/python/okstra_ctl/report_assembly.py +14 -93
- package/runtime/python/okstra_ctl/report_html/context_links.py +1 -1
- package/runtime/python/okstra_ctl/report_html/render.py +3 -2
- package/runtime/python/okstra_ctl/report_html/router.py +9 -33
- 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_template_loader.py +35 -0
- package/runtime/python/okstra_ctl/report_validation_identity.py +38 -0
- package/runtime/python/okstra_ctl/report_views.py +18 -2
- package/runtime/python/okstra_ctl/run.py +113 -503
- package/runtime/python/okstra_ctl/stage_map.py +13 -0
- package/runtime/python/okstra_ctl/stage_targets.py +9 -286
- package/runtime/python/okstra_ctl/technical_verification_facts.py +52 -0
- package/runtime/python/okstra_ctl/user_response.py +199 -4
- package/runtime/python/okstra_ctl/verification_target.py +1 -1
- 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/state.py +6 -2
- package/runtime/python/okstra_ctl/wizard/steps_analysis.py +0 -97
- package/runtime/python/okstra_ctl/wizard/steps_plan.py +27 -278
- 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_prompt_contract.py +36 -0
- package/runtime/python/okstra_ctl/workflow.py +26 -143
- package/runtime/skills/okstra-brief-gen/SKILL.md +3 -3
- package/runtime/skills/okstra-run/SKILL.md +1 -1
- package/runtime/skills/okstra-user-response/SKILL.md +23 -4
- 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 +287 -4091
- package/runtime/validators/validate_analysis_report.py +14 -126
- package/docs/task-process/README.md +0 -82
- package/docs/task-process/common-flow.md +0 -173
- 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/final-verification.json → python/okstra_ctl/phases/final_verification/profile.json} +0 -0
- /package/runtime/{templates/reports/html/tasks → python/okstra_ctl/phases/final_verification/report_assets}/final-verification.template.html +0 -0
- /package/runtime/{templates/reports/md/tasks → python/okstra_ctl/phases/final_verification/report_assets}/final-verification.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
|
@@ -19,6 +19,7 @@ import sys
|
|
|
19
19
|
from pathlib import Path
|
|
20
20
|
|
|
21
21
|
from .paths import find_asset_root
|
|
22
|
+
from .phases.catalog import PhaseAssetError, UnknownTaskType, profile_markdown, profile_sidecar
|
|
22
23
|
from .role_requirements import RoleProfileError, load_role_profile
|
|
23
24
|
from .run import PrepareError, _expand_profile_includes
|
|
24
25
|
|
|
@@ -28,7 +29,7 @@ _PROFILES_REL = ("prompts", "profiles")
|
|
|
28
29
|
# `prompts/profiles/_implementation-executor.md`. Reading the list from the
|
|
29
30
|
# profile body rather than hard-coding it here is deliberate: a list in code
|
|
30
31
|
# goes stale the moment a profile adds a sidecar, and does so silently.
|
|
31
|
-
_SIDECAR_RE = re.compile(r"`(prompts/profiles/_[\w-]+\.md)`")
|
|
32
|
+
_SIDECAR_RE = re.compile(r"`((?:prompts/profiles|scripts/okstra_ctl/phases/implementation/instructions)/_[\w-]+\.md)`")
|
|
32
33
|
|
|
33
34
|
|
|
34
35
|
def workspace_root(start: Path | None = None) -> Path:
|
|
@@ -44,7 +45,12 @@ def workspace_root(start: Path | None = None) -> Path:
|
|
|
44
45
|
|
|
45
46
|
|
|
46
47
|
def profile_path(root: Path, task_type: str) -> Path:
|
|
47
|
-
|
|
48
|
+
try:
|
|
49
|
+
path = profile_markdown(root, task_type)
|
|
50
|
+
except UnknownTaskType as exc:
|
|
51
|
+
raise PrepareError(f"unknown task-type: {task_type}") from exc
|
|
52
|
+
except PhaseAssetError as exc:
|
|
53
|
+
raise PrepareError(str(exc)) from exc
|
|
48
54
|
if not path.is_file():
|
|
49
55
|
raise PrepareError(f"unknown task-type: {task_type} (no {path})")
|
|
50
56
|
return path
|
|
@@ -71,7 +77,7 @@ def sidecar_bodies(root: Path, profile_text: str) -> list[str]:
|
|
|
71
77
|
if relative in seen:
|
|
72
78
|
continue
|
|
73
79
|
seen.add(relative)
|
|
74
|
-
path = root
|
|
80
|
+
path = profile_sidecar(root, relative)
|
|
75
81
|
if not path.is_file():
|
|
76
82
|
continue
|
|
77
83
|
body = _expand_profile_includes(path)
|
|
@@ -17,6 +17,8 @@ session id 등) 를 덧붙여 전달한다.
|
|
|
17
17
|
|
|
18
18
|
from __future__ import annotations
|
|
19
19
|
|
|
20
|
+
from .phases.catalog import profile_sidecar
|
|
21
|
+
|
|
20
22
|
import hashlib
|
|
21
23
|
import json
|
|
22
24
|
import re
|
|
@@ -2632,7 +2634,7 @@ def inject_lead_prompt_computed_tokens(ctx: dict) -> None:
|
|
|
2632
2634
|
home_prompts / "lead" / "convergence.md"
|
|
2633
2635
|
)
|
|
2634
2636
|
plan_body_verification_path = ctx.get("OKSTRA_PLAN_BODY_VERIFICATION_PATH") or str(
|
|
2635
|
-
home_prompts /
|
|
2637
|
+
profile_sidecar(home_prompts.parent, "prompts/lead/plan-body-verification.md")
|
|
2636
2638
|
)
|
|
2637
2639
|
report_writer_path = ctx.get("OKSTRA_REPORT_WRITER_PATH") or str(
|
|
2638
2640
|
home_prompts / "lead" / "report-writer.md"
|
|
@@ -2971,7 +2973,12 @@ def render_template_with_ctx(template_path: str, output_path: str, ctx: dict) ->
|
|
|
2971
2973
|
Optional defaults (VALIDATION_STATUS etc.) should be filled by calling
|
|
2972
2974
|
`apply_lead_prompt_defaults(ctx)` in the same setup step.
|
|
2973
2975
|
"""
|
|
2974
|
-
|
|
2976
|
+
from .phases.catalog import migrated_report_template
|
|
2977
|
+
|
|
2978
|
+
source = Path(template_path)
|
|
2979
|
+
if source.parent.name == "reports" and source.parent.parent.name == "templates":
|
|
2980
|
+
source = migrated_report_template(source.parent.parent.parent, source.name) or source
|
|
2981
|
+
template = source.read_text(encoding="utf-8")
|
|
2975
2982
|
|
|
2976
2983
|
fm_ctx = dict(ctx)
|
|
2977
2984
|
fm_ctx.setdefault("DOC_TYPE", _doc_type_from_template_path(template_path))
|
|
@@ -42,9 +42,10 @@ from typing import Any
|
|
|
42
42
|
# registers ``markupsafe`` and ``jinja2`` aliases in ``sys.modules`` so
|
|
43
43
|
# downstream ``from jinja2 import ...`` resolves to the vendored copy.
|
|
44
44
|
import okstra_vendor # noqa: F401 — side effect: sys.modules aliases
|
|
45
|
-
from jinja2 import ChainableUndefined, Environment
|
|
45
|
+
from jinja2 import ChainableUndefined, Environment
|
|
46
46
|
|
|
47
47
|
from okstra_ctl.clarification_items import USER_INPUT_BLOCKS, progress_blocking_ids
|
|
48
|
+
from okstra_ctl.report_template_loader import report_template_loader
|
|
48
49
|
from okstra_ctl.final_report_schema import (
|
|
49
50
|
SchemaError,
|
|
50
51
|
load_schema_for_data,
|
|
@@ -523,7 +524,7 @@ def _build_environment(template_dir: Path) -> Environment:
|
|
|
523
524
|
# the renderer can't tell "the writer forgot this required field"
|
|
524
525
|
# from "this field is intentionally absent on this task-type".
|
|
525
526
|
env = Environment(
|
|
526
|
-
loader=
|
|
527
|
+
loader=report_template_loader(template_dir),
|
|
527
528
|
undefined=ChainableUndefined,
|
|
528
529
|
trim_blocks=True,
|
|
529
530
|
lstrip_blocks=True,
|
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
|
|
4
4
|
import copy
|
|
5
|
-
import json
|
|
6
5
|
import os
|
|
7
6
|
import tempfile
|
|
7
|
+
from okstra_ctl.phases.implementation_planning.validation import validate_selected_direction_plan
|
|
8
|
+
|
|
8
9
|
from dataclasses import dataclass
|
|
9
10
|
from pathlib import Path
|
|
10
11
|
from typing import Any, Callable, Mapping, Sequence
|
|
@@ -25,22 +26,25 @@ from .report_synthesis_packet import (
|
|
|
25
26
|
)
|
|
26
27
|
from .conformance import strip_stage_declaration_label
|
|
27
28
|
from .design_prep import DesignPrepError, materialize_design_prep_requests
|
|
28
|
-
from .
|
|
29
|
-
from .technical_verification import validate_technical_verification_report
|
|
29
|
+
from .phases.implementation_option_selection.validation import validate_blocked_answer_channel
|
|
30
|
+
from .phases.technical_verification.validation import validate_technical_verification_report
|
|
31
|
+
from .report_routing import technical_verification_routing_errors
|
|
30
32
|
from .implementation_direction import (
|
|
31
33
|
load_selected_direction_snapshot,
|
|
32
|
-
validate_selected_direction_plan,
|
|
33
34
|
)
|
|
34
35
|
from .paths import task_dir
|
|
36
|
+
from .phases.final_verification.report import verification_scope
|
|
35
37
|
from .report_projections import (
|
|
36
38
|
ReportProjectionError,
|
|
37
39
|
project_convergence,
|
|
38
|
-
project_design,
|
|
39
40
|
project_execution,
|
|
40
41
|
project_token_usage,
|
|
41
42
|
)
|
|
42
|
-
from .
|
|
43
|
-
|
|
43
|
+
from .phases.implementation_planning.report import (
|
|
44
|
+
attach_design_snapshot, _sync_human_summary_with_plan_body_gate,
|
|
45
|
+
_fill_end_state_coverage,
|
|
46
|
+
)
|
|
47
|
+
|
|
44
48
|
from .write_policy import planned_path_declaration_errors
|
|
45
49
|
|
|
46
50
|
|
|
@@ -355,8 +359,7 @@ def _verification_scope(project_root: Path, manifest: Mapping[str, Any]) -> str:
|
|
|
355
359
|
relative = str(manifest.get("verificationTargetPath") or "").strip()
|
|
356
360
|
if not relative:
|
|
357
361
|
return ""
|
|
358
|
-
|
|
359
|
-
return str((target or {}).get("scope") or "").strip()
|
|
362
|
+
return verification_scope(project_root, relative)
|
|
360
363
|
|
|
361
364
|
|
|
362
365
|
def _clarification_carry_in(
|
|
@@ -689,15 +692,9 @@ def _attach_machine_planning(
|
|
|
689
692
|
if design_row is None:
|
|
690
693
|
return
|
|
691
694
|
try:
|
|
692
|
-
|
|
695
|
+
attach_design_snapshot(planning, _read_json(design_row))
|
|
693
696
|
except ReportProjectionError as exc:
|
|
694
697
|
_fail(design_row.owner, design_row.path, "$", exc)
|
|
695
|
-
planning["designPreparation"] = design["designPreparation"]
|
|
696
|
-
stages = {row.get("stage"): row for row in planning.get("stages") or [] if isinstance(row, dict)}
|
|
697
|
-
for coverage in design["stageCoverage"]:
|
|
698
|
-
stage = stages.get(coverage.get("stage"))
|
|
699
|
-
if stage is not None:
|
|
700
|
-
stage["designSurfaceCoverage"] = coverage.get("rows") or []
|
|
701
698
|
verification_row = inputs.get("plan-body-verification")
|
|
702
699
|
if verification_row is None:
|
|
703
700
|
return
|
|
@@ -717,83 +714,6 @@ def _attach_machine_planning(
|
|
|
717
714
|
_sync_human_summary_with_plan_body_gate(data, projection)
|
|
718
715
|
|
|
719
716
|
|
|
720
|
-
def _sync_human_summary_with_plan_body_gate(
|
|
721
|
-
data: dict[str, Any], projection: Mapping[str, Any],
|
|
722
|
-
) -> None:
|
|
723
|
-
"""게이트가 통과했는데 내러티브가 unseeded 로 남으면 사람용 칸을 맞춘다.
|
|
724
|
-
|
|
725
|
-
report-writer 는 PBV 전에 쓰고, 조립이 게이트를 덮어씌운다. blockers 와
|
|
726
|
-
nextStep 이 그때의 문장을 그대로 두면 통과한 계획이 아직 검증 전으로 보인다.
|
|
727
|
-
"""
|
|
728
|
-
gate = str(projection.get("gateResult") or "")
|
|
729
|
-
if gate not in {"passed", "passed-with-dissent"}:
|
|
730
|
-
return
|
|
731
|
-
summary = data.get("humanSummary")
|
|
732
|
-
if isinstance(summary, dict) and isinstance(summary.get("blockers"), list):
|
|
733
|
-
summary["blockers"] = [
|
|
734
|
-
row for row in summary["blockers"]
|
|
735
|
-
if "unseeded" not in str(row).lower()
|
|
736
|
-
]
|
|
737
|
-
card = data.get("verdictCard")
|
|
738
|
-
if not isinstance(card, dict):
|
|
739
|
-
return
|
|
740
|
-
next_step = str(card.get("nextStep") or "")
|
|
741
|
-
lowered = next_step.lower()
|
|
742
|
-
if "unseeded" not in lowered and "seed" not in lowered:
|
|
743
|
-
return
|
|
744
|
-
next_step = (
|
|
745
|
-
"Plan-body verification passed. Do not start implementation "
|
|
746
|
-
"until `frontmatter.approved` is true."
|
|
747
|
-
)
|
|
748
|
-
card["nextStep"] = next_step
|
|
749
|
-
final = data.get("finalVerdict")
|
|
750
|
-
if isinstance(final, dict):
|
|
751
|
-
final["nextStep"] = next_step
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
def _fill_end_state_coverage(data: dict[str, Any]) -> None:
|
|
755
|
-
"""writer 가 못 쓴 endStateCoverage 를 requirementCoverage brief:EB-001 에서 채운다.
|
|
756
|
-
|
|
757
|
-
내러티브 스키마가 이 칸을 열어 주면 writer 가 쓸 수 있다. 비어 있으면
|
|
758
|
-
validate-run 이 brief id 마다 한 행을 요구하므로 조립이 채운다.
|
|
759
|
-
"""
|
|
760
|
-
existing = data.get("endStateCoverage")
|
|
761
|
-
if isinstance(existing, list) and existing:
|
|
762
|
-
return
|
|
763
|
-
planning = data.get("implementationPlanning")
|
|
764
|
-
if not isinstance(planning, dict):
|
|
765
|
-
return
|
|
766
|
-
derived = _end_state_rows_from_requirement_coverage(planning)
|
|
767
|
-
if derived:
|
|
768
|
-
data["endStateCoverage"] = derived
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
def _end_state_rows_from_requirement_coverage(
|
|
772
|
-
planning: Mapping[str, Any],
|
|
773
|
-
) -> list[dict[str, Any]]:
|
|
774
|
-
rows: list[dict[str, Any]] = []
|
|
775
|
-
seen: set[str] = set()
|
|
776
|
-
for item in planning.get("requirementCoverage") or []:
|
|
777
|
-
if not isinstance(item, Mapping):
|
|
778
|
-
continue
|
|
779
|
-
ref = parse_source(str(item.get("source") or ""))
|
|
780
|
-
if not ref.is_end_state_id or ref.value in seen:
|
|
781
|
-
continue
|
|
782
|
-
seen.add(ref.value)
|
|
783
|
-
status = str(item.get("status") or "")
|
|
784
|
-
row: dict[str, Any] = {
|
|
785
|
-
"id": ref.value,
|
|
786
|
-
"disposition": "addressed" if status == "covered" else "deferred",
|
|
787
|
-
"coveredBy": str(item.get("id") or item.get("coveredBy") or ""),
|
|
788
|
-
}
|
|
789
|
-
if row["disposition"] != "addressed":
|
|
790
|
-
row["rationale"] = (
|
|
791
|
-
f"requirementCoverage `{item.get('id')}` status is `{status or 'empty'}`."
|
|
792
|
-
)
|
|
793
|
-
rows.append(row)
|
|
794
|
-
return rows
|
|
795
|
-
|
|
796
|
-
|
|
797
717
|
def _schema_owner(error: str) -> str:
|
|
798
718
|
field = error.split(" ", 1)[0]
|
|
799
719
|
owners = {
|
|
@@ -897,6 +817,7 @@ def assemble_report(
|
|
|
897
817
|
target = Path(value)
|
|
898
818
|
target = target if target.is_absolute() else project_root / target
|
|
899
819
|
verification_errors = validate_technical_verification_report(data, target, project_root)
|
|
820
|
+
verification_errors.extend(technical_verification_routing_errors(data))
|
|
900
821
|
if verification_errors:
|
|
901
822
|
raise ReportAssemblyError(tuple(
|
|
902
823
|
AssemblyIssue("report-writer", str(target), "technicalVerification", error)
|
|
@@ -247,7 +247,7 @@ def approved_plan_links(
|
|
|
247
247
|
except JsonBoundaryError:
|
|
248
248
|
return {}
|
|
249
249
|
from .common import anchor_index
|
|
250
|
-
from .
|
|
250
|
+
from okstra_ctl.phases.implementation_planning.report import ANCHORED_FIELDS
|
|
251
251
|
|
|
252
252
|
page = html_view_path(record)
|
|
253
253
|
href = Path(os.path.relpath(page, data_path.resolve().parent)).as_posix()
|
|
@@ -7,7 +7,7 @@ import sys
|
|
|
7
7
|
from pathlib import Path
|
|
8
8
|
|
|
9
9
|
import okstra_vendor # noqa: F401 # registers vendored dependency aliases
|
|
10
|
-
from jinja2 import Environment,
|
|
10
|
+
from jinja2 import Environment, StrictUndefined, select_autoescape
|
|
11
11
|
|
|
12
12
|
from ..final_report_paths import final_report_data_path, translation_sidecar_path
|
|
13
13
|
from ..final_report_schema import load_schema_for_data, validate
|
|
@@ -16,6 +16,7 @@ from ..report_translation import overlay
|
|
|
16
16
|
from ..report_view_artifacts import user_responses_dir_for_report
|
|
17
17
|
from ..usage_cells import format_duration_ms
|
|
18
18
|
from ..json_boundary import load_owned_object
|
|
19
|
+
from ..report_template_loader import report_template_loader
|
|
19
20
|
from .common import anchor_index, worker_finding_links
|
|
20
21
|
from .context_links import (
|
|
21
22
|
approved_plan_links,
|
|
@@ -145,7 +146,7 @@ def render_v2_html_view(
|
|
|
145
146
|
route = resolve_html_route(run_meta.task_type)
|
|
146
147
|
view = route.view_builder(data)
|
|
147
148
|
root = _templates_root(templates_root)
|
|
148
|
-
env = Environment(loader=
|
|
149
|
+
env = Environment(loader=report_template_loader(root), autoescape=select_autoescape(("html",)), undefined=StrictUndefined)
|
|
149
150
|
env.policies["json.dumps_kwargs"] = {"sort_keys": True, "ensure_ascii": False}
|
|
150
151
|
# Binding the index here is what lets a template cite an id without
|
|
151
152
|
# threading the index through every macro and call site. The record's
|
|
@@ -1,46 +1,22 @@
|
|
|
1
1
|
"""Fail-closed task-type routing for schema-v2 HTML views."""
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
|
|
4
|
+
import importlib
|
|
5
|
+
|
|
6
|
+
from ..phases.catalog import UnknownTaskType, view_builder_import
|
|
4
7
|
from ..report_contract import html_template_for
|
|
5
8
|
from .models import HtmlRoute
|
|
6
|
-
from .view_models.change_impact_analysis import build_change_impact_analysis_view
|
|
7
|
-
from .view_models.error_analysis import build_error_analysis_view
|
|
8
|
-
from .view_models.technical_verification import build_technical_verification_view
|
|
9
|
-
from .view_models.feature_analysis import build_feature_analysis_view
|
|
10
|
-
from .view_models.final_verification import build_final_verification_view
|
|
11
|
-
from .view_models.improvement_discovery import build_improvement_discovery_view
|
|
12
|
-
from .view_models.implementation_option_selection import (
|
|
13
|
-
build_implementation_option_selection_view,
|
|
14
|
-
)
|
|
15
|
-
from .view_models.implementation import build_implementation_view
|
|
16
|
-
from .view_models.implementation_planning import build_implementation_planning_view
|
|
17
|
-
from .view_models.project_analysis import build_project_analysis_view
|
|
18
|
-
from .view_models.release_handoff import build_release_handoff_view
|
|
19
|
-
from .view_models.requirements_discovery import build_requirements_discovery_view
|
|
20
9
|
|
|
21
10
|
|
|
22
11
|
class HtmlRenderError(ValueError):
|
|
23
12
|
"""Raised when a v2 task has no dedicated human view."""
|
|
24
13
|
|
|
25
14
|
|
|
26
|
-
VIEW_BUILDERS = {
|
|
27
|
-
"technical-verification": build_technical_verification_view,
|
|
28
|
-
"requirements-discovery": build_requirements_discovery_view,
|
|
29
|
-
"improvement-discovery": build_improvement_discovery_view,
|
|
30
|
-
"error-analysis": build_error_analysis_view,
|
|
31
|
-
"implementation-option-selection": build_implementation_option_selection_view,
|
|
32
|
-
"project-analysis": build_project_analysis_view,
|
|
33
|
-
"feature-analysis": build_feature_analysis_view,
|
|
34
|
-
"change-impact-analysis": build_change_impact_analysis_view,
|
|
35
|
-
"implementation-planning": build_implementation_planning_view,
|
|
36
|
-
"implementation": build_implementation_view,
|
|
37
|
-
"final-verification": build_final_verification_view,
|
|
38
|
-
"release-handoff": build_release_handoff_view,
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
|
|
42
15
|
def resolve_html_route(task_type: str) -> HtmlRoute:
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
16
|
+
"""선택한 작업 유형의 표시 함수만 적재한다."""
|
|
17
|
+
try:
|
|
18
|
+
module_name, function_name = view_builder_import(task_type)
|
|
19
|
+
except UnknownTaskType as exc:
|
|
20
|
+
raise HtmlRenderError(f"no v2 html view builder for {task_type}") from exc
|
|
21
|
+
builder = getattr(importlib.import_module(module_name), function_name)
|
|
46
22
|
return HtmlRoute(template_name=html_template_for(task_type), view_builder=builder)
|
|
@@ -4,10 +4,8 @@ from __future__ import annotations
|
|
|
4
4
|
from okstra_token_usage.blocks import accounting_workers
|
|
5
5
|
|
|
6
6
|
import json
|
|
7
|
-
from
|
|
8
|
-
from typing import Any, Mapping, Sequence
|
|
7
|
+
from typing import Any, Mapping
|
|
9
8
|
|
|
10
|
-
from .design_surfaces import DesignSurfaceTrigger, detect_design_surfaces
|
|
11
9
|
from .report_contract import execution_roles_from_manifest
|
|
12
10
|
from .usage_cells import duration_ms_from_bounds
|
|
13
11
|
|
|
@@ -220,39 +218,6 @@ def project_token_usage(team_state: Mapping[str, Any]) -> dict[str, Any]:
|
|
|
220
218
|
}
|
|
221
219
|
|
|
222
220
|
|
|
223
|
-
def _trigger_rows(trigger: DesignSurfaceTrigger) -> list[dict[str, Any]]:
|
|
224
|
-
return [
|
|
225
|
-
{"step": item.step, "field": item.field, "match": item.match}
|
|
226
|
-
for item in trigger.evidence
|
|
227
|
-
]
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
def project_design(
|
|
231
|
-
planning: Mapping[str, Any], detector_snapshot: Mapping[str, Any],
|
|
232
|
-
) -> dict[str, Any]:
|
|
233
|
-
"""탐지 결과가 실제 계획 표면과 일치할 때만 설계 블록을 반환한다."""
|
|
234
|
-
triggers = detect_design_surfaces(planning)
|
|
235
|
-
expected = {(row.stage, row.kind): _trigger_rows(row) for row in triggers}
|
|
236
|
-
coverage = detector_snapshot.get("stageCoverage")
|
|
237
|
-
coverage = coverage if isinstance(coverage, list) else []
|
|
238
|
-
actual: dict[tuple[int, str], list[dict[str, Any]]] = {}
|
|
239
|
-
for stage in coverage:
|
|
240
|
-
if not isinstance(stage, Mapping):
|
|
241
|
-
raise ReportProjectionError("owner=design-surface-detector invalid stageCoverage row")
|
|
242
|
-
for row in stage.get("rows") or []:
|
|
243
|
-
if isinstance(row, Mapping):
|
|
244
|
-
actual[(int(stage.get("stage") or 0), str(row.get("kind") or ""))] = list(row.get("triggerEvidence") or [])
|
|
245
|
-
if expected != actual:
|
|
246
|
-
raise ReportProjectionError("owner=design-surface-detector trigger coverage mismatch")
|
|
247
|
-
preparation = detector_snapshot.get("designPreparation")
|
|
248
|
-
if not isinstance(preparation, Mapping):
|
|
249
|
-
raise ReportProjectionError("owner=design-surface-detector missing designPreparation")
|
|
250
|
-
return {
|
|
251
|
-
"designPreparation": deepcopy(dict(preparation)),
|
|
252
|
-
"stageCoverage": deepcopy(coverage),
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
|
|
256
221
|
def _round_history(state: Mapping[str, Any]) -> dict[str, Any]:
|
|
257
222
|
if not (state.get("config") or {}).get("enabled", True):
|
|
258
223
|
return {"disabled": True}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""보고서 생산 단계와 분리된 공통 이동 적합성 검사."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from collections.abc import Mapping
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def technical_verification_routing_errors(data: Mapping[str, Any]) -> list[str]:
|
|
9
|
+
"""기술 검증의 후속 목적지가 후보 비교인지 검사하며 목적지를 추론하지 않는다."""
|
|
10
|
+
if (data.get("header") or {}).get("taskType") != "technical-verification":
|
|
11
|
+
return []
|
|
12
|
+
block = data.get("technicalVerification") or {}
|
|
13
|
+
if (block.get("routing") or {}).get("nextTaskType") != "implementation-option-selection":
|
|
14
|
+
return ["technical verification returns only to implementation-option-selection"]
|
|
15
|
+
return []
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def fanout_routing_errors(packet_name: str, target: str | None) -> list[str]:
|
|
19
|
+
"""작업 분해 패킷의 후속 단계가 허용된 값인지 검사한다."""
|
|
20
|
+
allowed = ("error-analysis", "implementation-option-selection")
|
|
21
|
+
if target not in allowed:
|
|
22
|
+
return [f"{packet_name}: recommended-next-phase not in {allowed}"]
|
|
23
|
+
return []
|
|
@@ -15,22 +15,14 @@ from okstra_token_usage.blocks import accounting_workers
|
|
|
15
15
|
|
|
16
16
|
from .analysis_packet import reference_source_extracts
|
|
17
17
|
from .json_boundary import JsonBoundaryError, load_owned_object, write_owned_object_atomic
|
|
18
|
-
from .
|
|
19
|
-
|
|
20
|
-
EVALUATION_CRITERIA,
|
|
21
|
-
MAX_CRITERION_VALUE,
|
|
22
|
-
MAX_RANKED_OPTIONS,
|
|
23
|
-
MAX_RAW_CANDIDATES_PER_ANALYSER,
|
|
24
|
-
MIN_CRITERION_VALUE,
|
|
25
|
-
MIN_FEASIBLE_VOTES,
|
|
26
|
-
NO_VALID_OPTIONS_ROUTING,
|
|
18
|
+
from .phases.implementation_option_selection.authoring import (
|
|
19
|
+
requirement_instructions as selection_requirement_instructions,
|
|
27
20
|
)
|
|
28
21
|
from .report_narrative import writer_owned_data, writer_owned_schema
|
|
29
22
|
from .schema_excerpt import build_schema_excerpt
|
|
30
23
|
from .stage_ledger import build_stage_ledger
|
|
31
24
|
from .scope_provenance import brief_end_state_id_sequence
|
|
32
25
|
|
|
33
|
-
from .exact_coverage import COVERAGE_VERDICT_PRECEDENCE
|
|
34
26
|
from .final_report_schema import follow_up_task_rules, task_block_rules, verdict_token_rule
|
|
35
27
|
from .report_contract import TASK_TYPE_DATA_PROPERTY
|
|
36
28
|
from .report_markdown import humanise
|
|
@@ -251,70 +243,9 @@ class ReportSynthesisPacket:
|
|
|
251
243
|
return self._planning_requirement_instructions()
|
|
252
244
|
if self.task_type != "implementation-option-selection":
|
|
253
245
|
return []
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
f"`{item}`" for item in self.original_requirement_ids
|
|
257
|
-
)
|
|
258
|
-
analysers = ", ".join(
|
|
259
|
-
f"`{item}`" for item in self.participating_analysers
|
|
246
|
+
return selection_requirement_instructions(
|
|
247
|
+
self.original_requirement_ids, self.participating_analysers,
|
|
260
248
|
)
|
|
261
|
-
return [
|
|
262
|
-
f"Original requirement ids, in order: {requirement_ids}.",
|
|
263
|
-
f"Participating analysers: {analysers}.",
|
|
264
|
-
f"Evaluation criteria, in exact order: {criteria}.",
|
|
265
|
-
"Every `rankedOptions` and `candidateAudit` row must contain one "
|
|
266
|
-
"`requirementCoverage` row per original requirement id and one "
|
|
267
|
-
"`criterionScores` row per evaluation criterion. Weights and scores "
|
|
268
|
-
f"are integers from {MIN_CRITERION_VALUE} through {MAX_CRITERION_VALUE}; "
|
|
269
|
-
"`weightedScore` is the recalculated weighted mean.",
|
|
270
|
-
"`proposedBy` names exactly one participating analyser id, never a "
|
|
271
|
-
"list. Counting `rankedOptions` and `candidateAudit` together, each "
|
|
272
|
-
f"analyser may propose at most {MAX_RAW_CANDIDATES_PER_ANALYSER} raw "
|
|
273
|
-
"candidates and the run may hold at most "
|
|
274
|
-
f"{MAX_RAW_CANDIDATES_PER_ANALYSER} times the analyser count.",
|
|
275
|
-
"`coverageSummary` is recalculated from the row's `requirementCoverage` "
|
|
276
|
-
"statuses and `scopeCommitments`, and every field must equal the "
|
|
277
|
-
"recalculation: `totalCount` = number of original requirement ids; "
|
|
278
|
-
"`coveredCount` = rows with status `covered`; `coveragePercent` = "
|
|
279
|
-
"round(coveredCount / totalCount * 100, 2); `unmappedCommitments` = "
|
|
280
|
-
"commitment ids whose `requirementIds` is empty or names an id outside "
|
|
281
|
-
"the original set; `scopePrecisionPercent` = round((commitments - "
|
|
282
|
-
"unmapped) / commitments * 100, 2); `contradictedRequirements` = "
|
|
283
|
-
"original ids with status `contradicted`, in original order; "
|
|
284
|
-
"`coverageVerdict` = the first that applies of "
|
|
285
|
-
+ ", ".join(f"`{verdict}`" for verdict in COVERAGE_VERDICT_PRECEDENCE)
|
|
286
|
-
+ " (any contradicted; coveredCount below totalCount; any unmapped; "
|
|
287
|
-
"otherwise).",
|
|
288
|
-
"A ranked option is valid only when every participating analyser "
|
|
289
|
-
"supplied one feasibility vote, at least "
|
|
290
|
-
f"{MIN_FEASIBLE_VOTES} votes are `feasible`, and both `safetyBlockers` "
|
|
291
|
-
"and `unresolvedFeasibilityFacts` are empty.",
|
|
292
|
-
("Classify each `unresolvedFeasibilityFacts` entry with `resolutionKind`: "
|
|
293
|
-
"`user-decision` requires nonempty `clarificationRefs` linking that fact "
|
|
294
|
-
"to actual C-NNN records, including answered records; `technical-verification` "
|
|
295
|
-
"requires no user question. Preserve the fact, whyItMatters, and evidence. "
|
|
296
|
-
"Historical unclassified facts remain readable but require classification "
|
|
297
|
-
"before blocked report reassembly. Saving does not lift blocked routing."),
|
|
298
|
-
("After a linked decision is answered, preserve its disposition and selected "
|
|
299
|
-
"value. Update humanSummary.actions, verdictCard.nextStep, and selection "
|
|
300
|
-
"guidance to name remaining technical verification separately from unanswered "
|
|
301
|
-
"user decisions; never reopen an answered question to permit saving."),
|
|
302
|
-
"Each `feasibilityVotes` row states that analyser's own verdict, "
|
|
303
|
-
"rationale, and strongest counterevidence as its result gives them. "
|
|
304
|
-
"Two non-`uncertain` votes with identical rationale and "
|
|
305
|
-
"counterevidence are rejected as one worker's text copied under "
|
|
306
|
-
"another's name.",
|
|
307
|
-
f"Rank at most {MAX_RANKED_OPTIONS} valid options by descending "
|
|
308
|
-
"`requirement-fit`, `correctness-risk`, `architecture-fit`, "
|
|
309
|
-
"`weightedScore`, then id. With valid options set routing to "
|
|
310
|
-
f"`{CANDIDATE_COMPARISON_ROUTING}`; without a valid option use "
|
|
311
|
-
f"`{NO_VALID_OPTIONS_ROUTING}`.",
|
|
312
|
-
"In `candidate-comparison`, keep `preselectedDirection` null and do "
|
|
313
|
-
"not route directly to `implementation-planning`. In "
|
|
314
|
-
"`preselected-validation`, emit exactly one validated option, an empty "
|
|
315
|
-
"`candidateAudit`, a cited `preselectedDirection`, and routing "
|
|
316
|
-
"`implementation-planning`.",
|
|
317
|
-
]
|
|
318
249
|
|
|
319
250
|
def _planning_requirement_instructions(self) -> list[str]:
|
|
320
251
|
"""계획 저작 계약에 브리프 요구사항 id 순서를 박아 넣는다.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""보고서 템플릿 로더.
|
|
2
|
+
|
|
3
|
+
이전된 단계의 본문은 단계 ``report_assets/`` 에 있고, 공통 레이아웃과
|
|
4
|
+
매크로는 ``templates/reports`` 에 남는다. 논리 이름은 그대로다.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
import okstra_vendor # noqa: F401 # registers vendored dependency aliases
|
|
11
|
+
from jinja2 import BaseLoader, FileSystemLoader, TemplateNotFound
|
|
12
|
+
|
|
13
|
+
from .phases.catalog import migrated_report_template
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ReportTemplateLoader(BaseLoader):
|
|
17
|
+
"""논리 템플릿 이름을 단계 정본 또는 공통 템플릿 트리에서 연다."""
|
|
18
|
+
|
|
19
|
+
def __init__(self, templates_root: Path) -> None:
|
|
20
|
+
self._root = templates_root
|
|
21
|
+
self._fallback = FileSystemLoader(str(templates_root))
|
|
22
|
+
|
|
23
|
+
def get_source(self, environment, template):
|
|
24
|
+
physical = migrated_report_template(self._root.parent.parent, template)
|
|
25
|
+
if physical is None:
|
|
26
|
+
return self._fallback.get_source(environment, template)
|
|
27
|
+
if not physical.is_file():
|
|
28
|
+
raise TemplateNotFound(template)
|
|
29
|
+
source = physical.read_text(encoding="utf-8")
|
|
30
|
+
mtime = physical.stat().st_mtime
|
|
31
|
+
return source, str(physical), lambda: physical.stat().st_mtime == mtime
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def report_template_loader(templates_root: Path) -> ReportTemplateLoader:
|
|
35
|
+
return ReportTemplateLoader(templates_root)
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""보고서 검증 시 파일 이름에서 실행 식별자를 읽는다."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
import re
|
|
5
|
+
|
|
6
|
+
_REPORT_BASENAME_SEQ_RE = re.compile(r"-(?P<seq>\d{3})(?:\.data)?\.(?:md|json)$")
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
_REPORT_BASENAME_TASK_TYPE_RE = re.compile(
|
|
10
|
+
r"^final-report-(?P<task_type>[a-z][a-z-]*?)-\d{3}(?:\.data)?\.(?:md|json)$"
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _report_run_seq(report_path: Path) -> str | None:
|
|
15
|
+
"""This run's seq, read off `final-report-<task-type>-<seq>.md`. ``None``
|
|
16
|
+
when the name does not carry one, so callers fall back to not filtering
|
|
17
|
+
rather than silently checking nothing."""
|
|
18
|
+
match = _REPORT_BASENAME_SEQ_RE.search(report_path.name)
|
|
19
|
+
return match.group("seq") if match else None
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _report_task_type(report_path: Path) -> str:
|
|
23
|
+
"""This run's task type, read off `final-report-<task-type>-<seq>`.
|
|
24
|
+
|
|
25
|
+
The report's own `header.taskType` is the first source, but it is not
|
|
26
|
+
always reachable. `report_narrative._allowed_top_level()` has no `header` —
|
|
27
|
+
it is not a writer-owned block — so a gate scored from a narrative, which is
|
|
28
|
+
the only input a report-contract-3.0 run has before assembly, carries no
|
|
29
|
+
task type at all. Globbing with an empty one matched nothing and reported
|
|
30
|
+
every verdict as unbacked under a `runs//worker-results/` path.
|
|
31
|
+
|
|
32
|
+
The filename carries it in every caller: the full-run path passes the report
|
|
33
|
+
itself, and `_report_path_for_state` builds the same canonical name from the
|
|
34
|
+
state file. Returns `""` when the name does not carry one, so a caller can
|
|
35
|
+
tell "not resolvable" from a real task type.
|
|
36
|
+
"""
|
|
37
|
+
match = _REPORT_BASENAME_TASK_TYPE_RE.match(report_path.name)
|
|
38
|
+
return match.group("task_type") if match else ""
|
|
@@ -30,9 +30,10 @@ import hashlib
|
|
|
30
30
|
import html
|
|
31
31
|
import json
|
|
32
32
|
import re
|
|
33
|
+
from collections.abc import Mapping
|
|
33
34
|
from dataclasses import dataclass
|
|
34
35
|
from pathlib import Path
|
|
35
|
-
from typing import Iterable, Optional
|
|
36
|
+
from typing import Any, Iterable, Optional
|
|
36
37
|
|
|
37
38
|
from .user_response_values import ( # noqa: F401 — 재노출(값 타입은 leaf 소유)
|
|
38
39
|
UserPlanDecision,
|
|
@@ -40,7 +41,7 @@ from .user_response_values import ( # noqa: F401 — 재노출(값 타입은 le
|
|
|
40
41
|
)
|
|
41
42
|
from .final_report_paths import final_report_data_path
|
|
42
43
|
from .json_boundary import JsonBoundaryError, load_owned_object
|
|
43
|
-
from .
|
|
44
|
+
from .plan_approval import (
|
|
44
45
|
PlanApprovalState,
|
|
45
46
|
plan_approval_state,
|
|
46
47
|
resolve_recommended_option,
|
|
@@ -1164,6 +1165,21 @@ def normalize_direction_selection_identity(
|
|
|
1164
1165
|
return normalized_id, normalized_name
|
|
1165
1166
|
|
|
1166
1167
|
|
|
1168
|
+
def direction_candidate_ineligibility(option: Mapping[str, Any]) -> str:
|
|
1169
|
+
"""Why a ranked candidate cannot become the planning direction, or ``""``.
|
|
1170
|
+
|
|
1171
|
+
The planning gate (`implementation_direction._selected_option`) and the
|
|
1172
|
+
in-session picker (`user_response.set_direction_selection`) share this rule,
|
|
1173
|
+
so a candidate the picker accepts is one the gate accepts.
|
|
1174
|
+
"""
|
|
1175
|
+
coverage = option.get("coverageSummary")
|
|
1176
|
+
if not isinstance(coverage, Mapping) or coverage.get("coverageVerdict") != "exact":
|
|
1177
|
+
return "selected candidate must have exact coverage"
|
|
1178
|
+
if option.get("safetyBlockers") or option.get("unresolvedFeasibilityFacts"):
|
|
1179
|
+
return "selected candidate has a safety blocker"
|
|
1180
|
+
return ""
|
|
1181
|
+
|
|
1182
|
+
|
|
1167
1183
|
def _quoted_sidecar_field(label: str, value: str) -> str:
|
|
1168
1184
|
cleaned = value.strip()
|
|
1169
1185
|
if not cleaned:
|