okstra 0.206.0 → 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 +1 -1
- package/dist/commands/lifecycle/install.mjs.map +1 -1
- package/docs/architecture.md +12 -12
- 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 +39 -18
- package/package.json +1 -1
- 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 +13 -54
- package/runtime/prompts/lead/phase-routing.md +64 -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/dispatch_state.py +22 -0
- package/runtime/python/okstra_ctl/doctor.py +2 -1
- 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 +0 -477
- package/runtime/python/okstra_ctl/initial_prompt_materialization.py +8 -1
- 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 +1 -1
- 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 +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/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} +2 -2
- 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} +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 +3 -3
- package/runtime/python/okstra_ctl/render.py +9 -2
- package/runtime/python/okstra_ctl/report_assembly.py +11 -90
- 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 +67 -349
- package/runtime/python/okstra_ctl/stage_map.py +13 -0
- 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_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_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/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
|
@@ -19,7 +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
|
|
22
|
+
from .phases.catalog import PhaseAssetError, UnknownTaskType, profile_markdown, profile_sidecar
|
|
23
23
|
from .role_requirements import RoleProfileError, load_role_profile
|
|
24
24
|
from .run import PrepareError, _expand_profile_includes
|
|
25
25
|
|
|
@@ -29,7 +29,7 @@ _PROFILES_REL = ("prompts", "profiles")
|
|
|
29
29
|
# `prompts/profiles/_implementation-executor.md`. Reading the list from the
|
|
30
30
|
# profile body rather than hard-coding it here is deliberate: a list in code
|
|
31
31
|
# goes stale the moment a profile adds a sidecar, and does so silently.
|
|
32
|
-
_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)`")
|
|
33
33
|
|
|
34
34
|
|
|
35
35
|
def workspace_root(start: Path | None = None) -> Path:
|
|
@@ -77,7 +77,7 @@ def sidecar_bodies(root: Path, profile_text: str) -> list[str]:
|
|
|
77
77
|
if relative in seen:
|
|
78
78
|
continue
|
|
79
79
|
seen.add(relative)
|
|
80
|
-
path = root
|
|
80
|
+
path = profile_sidecar(root, relative)
|
|
81
81
|
if not path.is_file():
|
|
82
82
|
continue
|
|
83
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))
|
|
@@ -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,24 @@ 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
|
|
35
36
|
from .phases.final_verification.report import verification_scope
|
|
36
37
|
from .report_projections import (
|
|
37
38
|
ReportProjectionError,
|
|
38
39
|
project_convergence,
|
|
39
|
-
project_design,
|
|
40
40
|
project_execution,
|
|
41
41
|
project_token_usage,
|
|
42
42
|
)
|
|
43
|
-
from .
|
|
43
|
+
from .phases.implementation_planning.report import (
|
|
44
|
+
attach_design_snapshot, _sync_human_summary_with_plan_body_gate,
|
|
45
|
+
_fill_end_state_coverage,
|
|
46
|
+
)
|
|
44
47
|
|
|
45
48
|
from .write_policy import planned_path_declaration_errors
|
|
46
49
|
|
|
@@ -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()
|
|
@@ -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,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 ""
|
|
@@ -41,7 +41,7 @@ from .user_response_values import ( # noqa: F401 — 재노출(값 타입은 le
|
|
|
41
41
|
)
|
|
42
42
|
from .final_report_paths import final_report_data_path
|
|
43
43
|
from .json_boundary import JsonBoundaryError, load_owned_object
|
|
44
|
-
from .
|
|
44
|
+
from .plan_approval import (
|
|
45
45
|
PlanApprovalState,
|
|
46
46
|
plan_approval_state,
|
|
47
47
|
resolve_recommended_option,
|