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
|
@@ -1,168 +1,6 @@
|
|
|
1
|
-
"""
|
|
2
|
-
|
|
3
|
-
사용자가 방향을 고르기 전에 비교 자료를 청하면 리드는 점수표·투표표·충족
|
|
4
|
-
요약을 보여 준다. 그 숫자는 전부 `implementationOptionSelection` 기록에 있고
|
|
5
|
-
`weightedScore` 는 발행 시점에 재계산 값과 대조된다
|
|
6
|
-
(`implementation_options.py` `_validate_option_scores`). 리드가 JSON 을 손으로
|
|
7
|
-
읽고 가중 점수를 다시 계산하던 자리를 이 명령이 대신한다. 서술(도입 문장,
|
|
8
|
-
방향별 요지)은 리드가 `narrative` 와 각 방향의 근거 필드로 쓴다.
|
|
9
|
-
|
|
10
|
-
읽기 전용이다. 어떤 파일도 쓰지 않는다.
|
|
11
|
-
"""
|
|
12
|
-
from __future__ import annotations
|
|
13
|
-
|
|
14
|
-
import argparse
|
|
15
|
-
import json
|
|
16
|
-
import sys
|
|
17
|
-
from collections.abc import Mapping, Sequence
|
|
18
|
-
from pathlib import Path
|
|
19
|
-
from typing import Any
|
|
20
|
-
|
|
21
|
-
from okstra_ctl import next_phase
|
|
22
|
-
from okstra_ctl.final_report_paths import final_report_data_path
|
|
23
|
-
from okstra_ctl.json_boundary import JsonBoundaryError, load_owned_object
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
class OptionComparisonError(ValueError):
|
|
27
|
-
"""비교 자료를 만들 수 없는 기록."""
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
def _rows(value: object) -> list[Mapping[str, Any]]:
|
|
31
|
-
if not isinstance(value, Sequence) or isinstance(value, (str, bytes)):
|
|
32
|
-
return []
|
|
33
|
-
return [row for row in value if isinstance(row, Mapping)]
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
def _text(value: object) -> str:
|
|
37
|
-
return value.strip() if isinstance(value, str) else ""
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
def _count(value: object) -> int:
|
|
41
|
-
return len(value) if isinstance(value, Sequence) and not isinstance(value, str) else 0
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
def _option(
|
|
45
|
-
option: Mapping[str, Any], recommended_id: str
|
|
46
|
-
) -> dict[str, Any]:
|
|
47
|
-
coverage = option.get("coverageSummary")
|
|
48
|
-
coverage = coverage if isinstance(coverage, Mapping) else {}
|
|
49
|
-
scores = _rows(option.get("criterionScores"))
|
|
50
|
-
return {
|
|
51
|
-
"id": _text(option.get("id")),
|
|
52
|
-
"name": _text(option.get("name")),
|
|
53
|
-
"recommended": _text(option.get("id")) == recommended_id,
|
|
54
|
-
"goal": _text(option.get("goal")),
|
|
55
|
-
"coreMechanism": _text(option.get("coreMechanism")),
|
|
56
|
-
# 발행 검증이 재계산 값과 같음을 보장한 기록값이다. 다시 계산하지 않는다.
|
|
57
|
-
"weightedScore": option.get("weightedScore"),
|
|
58
|
-
"scores": {_text(row.get("criterion")): row.get("score") for row in scores},
|
|
59
|
-
"scoreRationales": {
|
|
60
|
-
_text(row.get("criterion")): _text(row.get("rationale")) for row in scores
|
|
61
|
-
},
|
|
62
|
-
"coverage": {
|
|
63
|
-
"verdict": _text(coverage.get("coverageVerdict")),
|
|
64
|
-
"coveredCount": coverage.get("coveredCount"),
|
|
65
|
-
"totalCount": coverage.get("totalCount"),
|
|
66
|
-
"requirementIds": [
|
|
67
|
-
_text(row.get("requirementId"))
|
|
68
|
-
for row in _rows(option.get("requirementCoverage"))
|
|
69
|
-
if _text(row.get("requirementId"))
|
|
70
|
-
],
|
|
71
|
-
},
|
|
72
|
-
"safetyBlockers": _count(option.get("safetyBlockers")),
|
|
73
|
-
"unresolvedFeasibilityFacts": _count(option.get("unresolvedFeasibilityFacts")),
|
|
74
|
-
"votes": {
|
|
75
|
-
_text(vote.get("worker")): {
|
|
76
|
-
"verdict": _text(vote.get("verdict")),
|
|
77
|
-
"rationale": _text(vote.get("rationale")),
|
|
78
|
-
"counterevidence": _text(vote.get("counterevidence")),
|
|
79
|
-
}
|
|
80
|
-
for vote in _rows(option.get("feasibilityVotes"))
|
|
81
|
-
if _text(vote.get("worker"))
|
|
82
|
-
},
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
def build_comparison(report_data: Mapping[str, Any]) -> dict[str, Any]:
|
|
87
|
-
"""선택 리포트 기록 → 비교 자료 사실 묶음."""
|
|
88
|
-
selection = report_data.get("implementationOptionSelection")
|
|
89
|
-
if not isinstance(selection, Mapping):
|
|
90
|
-
raise OptionComparisonError(
|
|
91
|
-
"report has no implementationOptionSelection block; "
|
|
92
|
-
"pass an implementation-option-selection report record"
|
|
93
|
-
)
|
|
94
|
-
recommended_id = _text(selection.get("recommendedOptionId"))
|
|
95
|
-
options = [
|
|
96
|
-
_option(row, recommended_id) for row in _rows(selection.get("rankedOptions"))
|
|
97
|
-
]
|
|
98
|
-
if not options:
|
|
99
|
-
raise OptionComparisonError("report ranks no direction; there is nothing to compare")
|
|
100
|
-
# 투표 열 순서는 처음 등장한 순서다 — 방향마다 같은 분석자 집합이 투표한다.
|
|
101
|
-
workers: list[str] = []
|
|
102
|
-
for option in options:
|
|
103
|
-
for worker in option["votes"]:
|
|
104
|
-
if worker not in workers:
|
|
105
|
-
workers.append(worker)
|
|
106
|
-
narrative = selection.get("userNarrative")
|
|
107
|
-
narrative = narrative if isinstance(narrative, Mapping) else {}
|
|
108
|
-
return {
|
|
109
|
-
"mode": _text(selection.get("mode")),
|
|
110
|
-
"routing": _text(selection.get("routing")),
|
|
111
|
-
"recommendedOptionId": recommended_id,
|
|
112
|
-
"criteria": [
|
|
113
|
-
{"criterion": _text(row.get("criterion")), "weight": row.get("weight")}
|
|
114
|
-
for row in _rows(selection.get("evaluationCriteria"))
|
|
115
|
-
],
|
|
116
|
-
"workers": workers,
|
|
117
|
-
"options": options,
|
|
118
|
-
"narrative": {
|
|
119
|
-
key: _text(block.get("text"))
|
|
120
|
-
for key, block in narrative.items()
|
|
121
|
-
if isinstance(block, Mapping) and _text(block.get("text"))
|
|
122
|
-
},
|
|
123
|
-
# "남은 절차" 문단의 근거. 다음 phase 포인터와 같은 문장이다.
|
|
124
|
-
"nextStep": next_phase.project(report_data)["rationale"],
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
def _load(report: Path) -> Mapping[str, Any]:
|
|
129
|
-
data_path = final_report_data_path(report)
|
|
130
|
-
try:
|
|
131
|
-
data = load_owned_object(data_path, artifact="implementation option data")
|
|
132
|
-
except (OSError, JsonBoundaryError) as exc:
|
|
133
|
-
raise OptionComparisonError(f"cannot read report record {data_path}: {exc}") from exc
|
|
134
|
-
return data
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
def build_parser() -> argparse.ArgumentParser:
|
|
138
|
-
parser = argparse.ArgumentParser(
|
|
139
|
-
prog="okstra option-comparison",
|
|
140
|
-
description=(
|
|
141
|
-
"Print the comparison facts of an implementation-option-selection "
|
|
142
|
-
"report as JSON: criteria and weights, per-direction scores and "
|
|
143
|
-
"recorded weighted score, requirement coverage, blocker counts, "
|
|
144
|
-
"per-worker feasibility votes, the report narrative, and the next step."
|
|
145
|
-
),
|
|
146
|
-
)
|
|
147
|
-
parser.add_argument(
|
|
148
|
-
"--report",
|
|
149
|
-
required=True,
|
|
150
|
-
help="final-report-implementation-option-selection-<seq>.data.json (a .md sibling is accepted)",
|
|
151
|
-
)
|
|
152
|
-
return parser
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
def main(argv: list[str] | None = None) -> int:
|
|
156
|
-
args = build_parser().parse_args(argv)
|
|
157
|
-
try:
|
|
158
|
-
payload = build_comparison(_load(Path(args.report)))
|
|
159
|
-
except OptionComparisonError as exc:
|
|
160
|
-
print(f"option-comparison: {exc}", file=sys.stderr)
|
|
161
|
-
return 2
|
|
162
|
-
json.dump(payload, sys.stdout, ensure_ascii=False, indent=2)
|
|
163
|
-
sys.stdout.write("\n")
|
|
164
|
-
return 0
|
|
1
|
+
"""기존 명령 이름을 단계의 실행 진입점으로 연결한다."""
|
|
2
|
+
from okstra_ctl.phases.implementation_option_selection.comparison import main
|
|
165
3
|
|
|
166
4
|
|
|
167
5
|
if __name__ == "__main__":
|
|
168
|
-
|
|
6
|
+
raise SystemExit(main())
|
|
@@ -1,194 +1,6 @@
|
|
|
1
|
-
"""
|
|
2
|
-
|
|
3
|
-
`implementation-option-selection` 의 1라운드는 설계자들이 병렬로 돌아 서로의
|
|
4
|
-
후보를 보지 못한다. 그래서 자기가 낸 후보에만 실현 가능성 표를 남기고, 병합된
|
|
5
|
-
집합에는 분석자마다 다른 구멍이 생긴다. 유효성 규칙은 전원 투표를 요구하므로
|
|
6
|
-
(`implementation_options._validate_option_feasibility`) 그런 후보는 순위표에
|
|
7
|
-
오르지 못하고, 남는 후보가 하나도 없으면 run 이 `routing: blocked` 로 끝난다 —
|
|
8
|
-
실측(2026-09-10, dev-10629-4): 설계자 3명 로스터에서 IO-001·IO-002·IO-003 이
|
|
9
|
-
각각 `feasible` 2표를 받고도 빠진 분석자가 하나씩 달라 전부 탈락했다.
|
|
10
|
-
|
|
11
|
-
거절은 그 상태를 알려 줄 뿐 메우지 못한다. 이 명령이 앞으로 가는 길이다:
|
|
12
|
-
표만 모자란 후보를 세고, 어느 분석자에게 어떤 후보를 부쳐야 하는지 말한다.
|
|
13
|
-
재검증(reverify) 라운드는 주장을 반박하는 라운드이지 후보에 표를 남기는
|
|
14
|
-
라운드가 아니므로, 그 구멍을 메우는 디스패치는 리드가 이 목록을 보고 연다.
|
|
15
|
-
|
|
16
|
-
읽는 자리는 두 가지다. 조립 전이면 작성자 서사(`--narrative`), 이미 발행된
|
|
17
|
-
run 이면 리포트 레코드(`--report`). 로스터는 task-manifest 의
|
|
18
|
-
`recommendedWorkers` 에서 `report-writer` 를 뺀 것이고, 그것이 검증기가
|
|
19
|
-
`participating analysers` 로 쓰는 값과 같은 정의다(`validators/validate-run.py`).
|
|
20
|
-
"""
|
|
21
|
-
from __future__ import annotations
|
|
22
|
-
|
|
23
|
-
import argparse
|
|
24
|
-
import json
|
|
25
|
-
import sys
|
|
26
|
-
from collections.abc import Mapping
|
|
27
|
-
from pathlib import Path
|
|
28
|
-
from typing import Any
|
|
29
|
-
|
|
30
|
-
from okstra_ctl.final_report_schema import load_schema_version
|
|
31
|
-
from okstra_ctl.implementation_options import VoteGap, vote_gaps
|
|
32
|
-
from okstra_ctl.json_boundary import JsonBoundaryError, load_owned_object
|
|
33
|
-
from okstra_ctl.report_contract import CURRENT_REPORT_SCHEMA_VERSION
|
|
34
|
-
from okstra_ctl.report_narrative import parse_narrative_structure
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
class OptionVotesError(ValueError):
|
|
38
|
-
"""투표 구멍을 셀 입력이 없거나 읽히지 않는다."""
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
def _record_from_report(path: Path) -> dict[str, Any]:
|
|
42
|
-
try:
|
|
43
|
-
return load_owned_object(path, artifact="final-report data.json")
|
|
44
|
-
except (JsonBoundaryError, OSError) as exc:
|
|
45
|
-
raise OptionVotesError(f"report record is unreadable: {exc}") from exc
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
def _record_from_narrative(path: Path) -> dict[str, Any]:
|
|
49
|
-
"""작성자 서사를 레코드 모양으로 읽는다.
|
|
50
|
-
|
|
51
|
-
값 결함은 무시한다 — 교정 원장이 고칠 자리이고, 표 구멍을 세는 데에는
|
|
52
|
-
후보 id 와 `feasibilityVotes` 만 있으면 된다. 여기서 서사 전체를 거절하면
|
|
53
|
-
아직 교정 중인 run 은 이 명령을 쓸 수 없다.
|
|
54
|
-
"""
|
|
55
|
-
try:
|
|
56
|
-
markdown = path.read_text(encoding="utf-8")
|
|
57
|
-
except OSError as exc:
|
|
58
|
-
raise OptionVotesError(f"narrative is unreadable: {exc}") from exc
|
|
59
|
-
schema = load_schema_version(CURRENT_REPORT_SCHEMA_VERSION)
|
|
60
|
-
record, _defects = parse_narrative_structure(markdown, schema)
|
|
61
|
-
return record
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
def participating_analysers(manifest_path: Path) -> tuple[str, ...]:
|
|
65
|
-
try:
|
|
66
|
-
manifest = load_owned_object(manifest_path, artifact="task-manifest")
|
|
67
|
-
except (JsonBoundaryError, OSError) as exc:
|
|
68
|
-
raise OptionVotesError(f"task manifest is unreadable: {exc}") from exc
|
|
69
|
-
roster = manifest.get("recommendedWorkers")
|
|
70
|
-
if not isinstance(roster, list):
|
|
71
|
-
raise OptionVotesError("task manifest has no recommendedWorkers roster")
|
|
72
|
-
return tuple(
|
|
73
|
-
str(worker) for worker in roster if str(worker) != "report-writer"
|
|
74
|
-
)
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
def _selection(record: Mapping[str, Any]) -> Mapping[str, Any]:
|
|
78
|
-
selection = record.get("implementationOptionSelection")
|
|
79
|
-
if not isinstance(selection, Mapping):
|
|
80
|
-
raise OptionVotesError(
|
|
81
|
-
"the source has no implementationOptionSelection block — "
|
|
82
|
-
"this command reads an implementation-option-selection run"
|
|
83
|
-
)
|
|
84
|
-
return selection
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
def _dispatch_lines(gaps: list[VoteGap]) -> list[str]:
|
|
88
|
-
"""분석자별로 부칠 후보 목록. 디스패치 단위가 분석자이기 때문이다."""
|
|
89
|
-
by_analyser: dict[str, list[str]] = {}
|
|
90
|
-
for gap in gaps:
|
|
91
|
-
for analyser in gap.missing:
|
|
92
|
-
by_analyser.setdefault(analyser, []).append(gap.option_id)
|
|
93
|
-
return [
|
|
94
|
-
f" {analyser}: {', '.join(options)}"
|
|
95
|
-
for analyser, options in sorted(by_analyser.items())
|
|
96
|
-
]
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
def _render(gaps: list[VoteGap]) -> str:
|
|
100
|
-
if not gaps:
|
|
101
|
-
return (
|
|
102
|
-
"No candidate is short of votes alone. A blocked run here is "
|
|
103
|
-
"blocked by something a vote cannot settle — safety blockers, "
|
|
104
|
-
"unresolved feasibility facts, or too few feasible verdicts."
|
|
105
|
-
)
|
|
106
|
-
lines = [
|
|
107
|
-
f"{len(gaps)} candidate(s) need only the missing feasibility votes:",
|
|
108
|
-
"",
|
|
109
|
-
]
|
|
110
|
-
lines += [
|
|
111
|
-
f" {gap.option_id}: {gap.feasible_votes} feasible so far, "
|
|
112
|
-
f"missing {', '.join(gap.missing)}"
|
|
113
|
-
for gap in gaps
|
|
114
|
-
]
|
|
115
|
-
lines += ["", "Dispatch one vote-completion assignment per analyser:"]
|
|
116
|
-
lines += _dispatch_lines(gaps)
|
|
117
|
-
lines += [
|
|
118
|
-
"",
|
|
119
|
-
"Each assignment asks that analyser for its own feasibility verdict, "
|
|
120
|
-
"rationale, and counterevidence on the named candidate — nothing else. "
|
|
121
|
-
"It generates no candidate, so the run stays in `candidate-comparison` "
|
|
122
|
-
"mode; `preselected-validation` is a whole-run mode that would collapse "
|
|
123
|
-
"the comparison to one direction.",
|
|
124
|
-
]
|
|
125
|
-
return "\n".join(lines)
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
def _gaps(args: argparse.Namespace) -> int:
|
|
129
|
-
if bool(args.report) == bool(args.narrative):
|
|
130
|
-
raise OptionVotesError("pass exactly one of --report or --narrative")
|
|
131
|
-
record = (
|
|
132
|
-
_record_from_report(args.report)
|
|
133
|
-
if args.report
|
|
134
|
-
else _record_from_narrative(args.narrative)
|
|
135
|
-
)
|
|
136
|
-
gaps = vote_gaps(
|
|
137
|
-
_selection(record), participating_analysers(args.task_manifest)
|
|
138
|
-
)
|
|
139
|
-
if args.json:
|
|
140
|
-
print(json.dumps(
|
|
141
|
-
{
|
|
142
|
-
"gaps": [
|
|
143
|
-
{
|
|
144
|
-
"optionId": gap.option_id,
|
|
145
|
-
"missing": list(gap.missing),
|
|
146
|
-
"feasibleVotes": gap.feasible_votes,
|
|
147
|
-
}
|
|
148
|
-
for gap in gaps
|
|
149
|
-
]
|
|
150
|
-
},
|
|
151
|
-
ensure_ascii=False,
|
|
152
|
-
indent=2,
|
|
153
|
-
))
|
|
154
|
-
else:
|
|
155
|
-
print(_render(gaps))
|
|
156
|
-
return 0
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
_CLI_DESCRIPTION = (
|
|
160
|
-
"Report the implementation candidates that only lack feasibility votes, "
|
|
161
|
-
"and which analyser owes each one."
|
|
162
|
-
)
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
def _parser() -> argparse.ArgumentParser:
|
|
166
|
-
parser = argparse.ArgumentParser(
|
|
167
|
-
description=_CLI_DESCRIPTION, prog="okstra option-votes"
|
|
168
|
-
)
|
|
169
|
-
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
170
|
-
gaps_parser = subparsers.add_parser("gaps")
|
|
171
|
-
gaps_parser.add_argument(
|
|
172
|
-
"--task-manifest", type=Path, required=True,
|
|
173
|
-
help="the task's task-manifest.json — its roster names the analysers")
|
|
174
|
-
gaps_parser.add_argument(
|
|
175
|
-
"--report", type=Path,
|
|
176
|
-
help="a published final-report `.data.json`")
|
|
177
|
-
gaps_parser.add_argument(
|
|
178
|
-
"--narrative", type=Path,
|
|
179
|
-
help="the report writer's narrative markdown, before assembly")
|
|
180
|
-
gaps_parser.add_argument("--json", action="store_true")
|
|
181
|
-
return parser
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
def main(argv: list[str] | None = None) -> int:
|
|
185
|
-
args = _parser().parse_args(argv)
|
|
186
|
-
try:
|
|
187
|
-
return _gaps(args)
|
|
188
|
-
except OptionVotesError as exc:
|
|
189
|
-
print(f"okstra option-votes: {exc}", file=sys.stderr)
|
|
190
|
-
return 1
|
|
1
|
+
"""기존 명령 이름을 단계의 실행 진입점으로 연결한다."""
|
|
2
|
+
from okstra_ctl.phases.implementation_option_selection.votes import main
|
|
191
3
|
|
|
192
4
|
|
|
193
5
|
if __name__ == "__main__":
|
|
194
|
-
raise SystemExit(main(
|
|
6
|
+
raise SystemExit(main())
|
|
@@ -29,6 +29,8 @@ from okstra_project.dirs import (
|
|
|
29
29
|
)
|
|
30
30
|
from okstra_project.slug import slugify
|
|
31
31
|
|
|
32
|
+
from .asset_roots import walk_to_asset_root as _walk_to_asset_root
|
|
33
|
+
|
|
32
34
|
__all__ = [
|
|
33
35
|
"OKSTRA_RELATIVE",
|
|
34
36
|
"TASKS_RELATIVE",
|
|
@@ -103,11 +105,9 @@ def walk_to_asset_root(
|
|
|
103
105
|
override. A caller that must stay inside the tree it started from (a phase
|
|
104
106
|
profile resolving its includes) calls this directly.
|
|
105
107
|
"""
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
return parent
|
|
110
|
-
return None
|
|
108
|
+
return _walk_to_asset_root(
|
|
109
|
+
relative, start=Path(start or __file__), is_present=is_present,
|
|
110
|
+
)
|
|
111
111
|
|
|
112
112
|
|
|
113
113
|
_STAGED_TASK_TYPES = ("implementation", "final-verification")
|
|
@@ -623,6 +623,8 @@ def compute_run_paths(
|
|
|
623
623
|
incremental_decision = run_state / f"incremental-decision-{task_type_segment}-{seqs['state']}.json"
|
|
624
624
|
|
|
625
625
|
run_errors_log = run_logs / f"errors-{task_type_segment}-{seqs['state']}.jsonl"
|
|
626
|
+
from .phases.catalog import profile_sidecar
|
|
627
|
+
|
|
626
628
|
runtime_home = okstra_home()
|
|
627
629
|
|
|
628
630
|
claude_worker_errors_sidecar = worker_results / f"claude-worker-errors{suffixes['worker_results']}.json"
|
|
@@ -680,7 +682,7 @@ def compute_run_paths(
|
|
|
680
682
|
runtime_home / "prompts" / "lead" / "convergence.md"
|
|
681
683
|
),
|
|
682
684
|
"OKSTRA_PLAN_BODY_VERIFICATION_PATH": str(
|
|
683
|
-
runtime_home
|
|
685
|
+
profile_sidecar(runtime_home, "prompts/lead/plan-body-verification.md")
|
|
684
686
|
),
|
|
685
687
|
"OKSTRA_REPORT_WRITER_PATH": str(
|
|
686
688
|
runtime_home / "prompts" / "lead" / "report-writer.md"
|
|
@@ -8,7 +8,7 @@ from __future__ import annotations
|
|
|
8
8
|
from dataclasses import dataclass
|
|
9
9
|
from pathlib import Path
|
|
10
10
|
|
|
11
|
-
from ..
|
|
11
|
+
from ..asset_roots import walk_to_asset_root
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
class PhaseAssetError(ValueError):
|
|
@@ -51,23 +51,23 @@ def _record(
|
|
|
51
51
|
# 루트의 단계 파일이 없어도 prompts/profiles 사본으로 넘어가지 않는다. 패키지를
|
|
52
52
|
# 품지 않은 루트는 그 루트의 prompts/profiles 사본이 자료다 (``profile_markdown``).
|
|
53
53
|
PHASES: tuple[PhaseRecord, ...] = (
|
|
54
|
-
_record("requirements-discovery", "requirements_discovery",
|
|
55
|
-
_record("improvement-discovery", "improvement_discovery",
|
|
56
|
-
_record("project-analysis", "project_analysis",
|
|
57
|
-
_record("feature-analysis", "feature_analysis",
|
|
58
|
-
_record("change-impact-analysis", "change_impact_analysis",
|
|
59
|
-
_record("error-analysis", "error_analysis",
|
|
60
|
-
_record("technical-verification", "technical_verification",
|
|
54
|
+
_record("requirements-discovery", "requirements_discovery", True, "build_requirements_discovery_view"),
|
|
55
|
+
_record("improvement-discovery", "improvement_discovery", True, "build_improvement_discovery_view"),
|
|
56
|
+
_record("project-analysis", "project_analysis", True, "build_project_analysis_view"),
|
|
57
|
+
_record("feature-analysis", "feature_analysis", True, "build_feature_analysis_view"),
|
|
58
|
+
_record("change-impact-analysis", "change_impact_analysis", True, "build_change_impact_analysis_view"),
|
|
59
|
+
_record("error-analysis", "error_analysis", True, "build_error_analysis_view"),
|
|
60
|
+
_record("technical-verification", "technical_verification", True, "build_technical_verification_view"),
|
|
61
61
|
_record(
|
|
62
62
|
"implementation-option-selection",
|
|
63
63
|
"implementation_option_selection",
|
|
64
|
-
|
|
64
|
+
True,
|
|
65
65
|
"build_implementation_option_selection_view",
|
|
66
66
|
),
|
|
67
|
-
_record("implementation-planning", "implementation_planning",
|
|
68
|
-
_record("implementation", "implementation",
|
|
67
|
+
_record("implementation-planning", "implementation_planning", True, "build_implementation_planning_view"),
|
|
68
|
+
_record("implementation", "implementation", True, "build_implementation_view"),
|
|
69
69
|
_record("final-verification", "final_verification", True, "build_final_verification_view"),
|
|
70
|
-
_record("release-handoff", "release_handoff",
|
|
70
|
+
_record("release-handoff", "release_handoff", True, "build_release_handoff_view"),
|
|
71
71
|
)
|
|
72
72
|
|
|
73
73
|
_BY_TASK_TYPE = {row.task_type: row for row in PHASES}
|
|
@@ -208,9 +208,53 @@ def _asset_root_containing(start: Path) -> Path:
|
|
|
208
208
|
|
|
209
209
|
|
|
210
210
|
def _report_template_filename(task_type: str, logical_name: str) -> str | None:
|
|
211
|
+
if task_type == "requirements-discovery" and logical_name == "fan-out-unit.template.md":
|
|
212
|
+
return logical_name
|
|
213
|
+
if logical_name == f"{task_type}-input.template.md":
|
|
214
|
+
return logical_name
|
|
211
215
|
if logical_name == f"html/tasks/{task_type}.template.html":
|
|
212
216
|
return f"{task_type}.template.html"
|
|
213
217
|
if logical_name == f"md/tasks/{task_type}.template.md":
|
|
214
218
|
return f"{task_type}.template.md"
|
|
215
219
|
return None
|
|
216
220
|
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def host_rules(asset_root: Path, task_type: str) -> Path:
|
|
224
|
+
"""선택된 단계의 호스트 지침을 같은 자산 루트에서 찾는다."""
|
|
225
|
+
record = phase_record(task_type)
|
|
226
|
+
root = package_root(asset_root) if record.migrated else None
|
|
227
|
+
if root is not None:
|
|
228
|
+
return root / "phases" / record.package_name / "host-rules.md"
|
|
229
|
+
return asset_root / "prompts" / "host-orchestration" / f"{task_type}.md"
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def profile_sidecar(asset_root: Path, logical_name: str) -> Path:
|
|
233
|
+
"""동결 프로필에 남은 구현 보조 지침 경로도 현재 정본으로 해석한다."""
|
|
234
|
+
path = Path(logical_name)
|
|
235
|
+
if logical_name in {
|
|
236
|
+
"prompts/lead/plan-body-verification.md",
|
|
237
|
+
"scripts/okstra_ctl/phases/implementation_planning/instructions/plan-body-verification.md",
|
|
238
|
+
}:
|
|
239
|
+
root = package_root(asset_root)
|
|
240
|
+
if root is not None:
|
|
241
|
+
return root / "phases" / "implementation_planning" / "instructions" / path.name
|
|
242
|
+
if path.parent in {
|
|
243
|
+
Path("prompts/profiles"),
|
|
244
|
+
Path("scripts/okstra_ctl/phases/implementation/instructions"),
|
|
245
|
+
} and path.name in {
|
|
246
|
+
"_implementation-executor.md", "_implementation-verifier.md",
|
|
247
|
+
"_implementation-deliverable.md", "_implementation-diff-review.md",
|
|
248
|
+
"_implementation-self-check.md",
|
|
249
|
+
}:
|
|
250
|
+
root = package_root(asset_root)
|
|
251
|
+
if root is not None:
|
|
252
|
+
return root / "phases" / "implementation" / "instructions" / path.name
|
|
253
|
+
return asset_root / path
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def phase_boundary(asset_root: Path, task_type: str) -> Path | None:
|
|
257
|
+
"""주어진 자산 루트 안에 있는 단계 경계 자원의 경로."""
|
|
258
|
+
record = phase_record(task_type)
|
|
259
|
+
root = package_root(asset_root)
|
|
260
|
+
return root / "phases" / record.package_name / "boundary.json" if root else None
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"allowed": " - preserved behavior, impacted items, and dependency propagation\n - test and operational impact\n - constraints and open decisions passed to implementation-planning\n",
|
|
3
|
+
"forbidden": [
|
|
4
|
+
"source or configuration edits",
|
|
5
|
+
"tests, builds, migrations, or deployments",
|
|
6
|
+
"starting any other lifecycle phase inside this run",
|
|
7
|
+
"implementation alternatives",
|
|
8
|
+
"file change specifications",
|
|
9
|
+
"stepwise execution plans"
|
|
10
|
+
]
|
|
11
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""Feature-baseline input steps for change impact analysis."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
from okstra_ctl.wizard.ids import S_FEATURE_EVIDENCE, S_FEATURE_EVIDENCE_PICK
|
|
7
|
+
from okstra_ctl.wizard.state import Prompt, WizardState
|
|
8
|
+
from okstra_ctl.wizard.prompts import _p
|
|
9
|
+
from okstra_ctl.wizard.steps_analysis import (
|
|
10
|
+
_accept_analysis_evidence,
|
|
11
|
+
_build_analysis_evidence_pick,
|
|
12
|
+
_submit_analysis_evidence_pick,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _build_feature_evidence_pick(state: WizardState) -> Prompt:
|
|
17
|
+
return _build_analysis_evidence_pick(
|
|
18
|
+
state, step=S_FEATURE_EVIDENCE_PICK, relation="feature-baseline"
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _submit_feature_evidence_pick(state: WizardState, value: str) -> Optional[str]:
|
|
23
|
+
return _submit_analysis_evidence_pick(
|
|
24
|
+
state, value, step=S_FEATURE_EVIDENCE_PICK,
|
|
25
|
+
field_name="feature_evidence_path",
|
|
26
|
+
pending_name="feature_evidence_pending_text",
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _build_feature_evidence(state: WizardState) -> Prompt:
|
|
31
|
+
t = _p(state.workspace_root, S_FEATURE_EVIDENCE)
|
|
32
|
+
return Prompt(step=S_FEATURE_EVIDENCE, kind="text", label=t["label"],
|
|
33
|
+
echo_template=t["echo_template"])
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _submit_feature_evidence(state: WizardState, value: str) -> Optional[str]:
|
|
37
|
+
candidate = _accept_analysis_evidence(state, value, "feature_evidence_path")
|
|
38
|
+
state.feature_evidence_pending_text = False
|
|
39
|
+
return f"feature-evidence: {candidate.report_path}"
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
"""Human-first change-impact-analysis view model."""
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
|
|
4
|
-
from
|
|
5
|
-
from
|
|
6
|
-
from
|
|
4
|
+
from okstra_ctl.report_html.common import analysis_review_ids, evidence_index
|
|
5
|
+
from okstra_ctl.report_html.models import HumanReportView, VisualEdge, VisualNode
|
|
6
|
+
from okstra_ctl.report_html.visualizations import flow_figure
|
|
7
7
|
|
|
8
8
|
# Record fields this template anchors as `id-<row id>` (see
|
|
9
9
|
# `HumanReportView.anchored_fields`); ids elsewhere land in the ledger.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Change impact analysis phase
|
|
2
|
+
|
|
3
|
+
## Purpose and boundaries
|
|
4
|
+
|
|
5
|
+
This phase maps a proposed change's preserved behavior, affected and unaffected boundaries, dependency propagation, tests and operational impact. It records planning constraints and open decisions without producing an execution plan. `profile.md` owns these analysis instructions and the adversarial convergence setting.
|
|
6
|
+
|
|
7
|
+
`entry.py` owns the feature-baseline selection and text-entry handlers. The common wizard registry preserves the existing question order: feature evidence before project evidence. Shared analysis-input code retains evidence ownership, review status, freshness, relation and path checks. The CLI evidence arguments and run artifacts are unchanged.
|
|
8
|
+
|
|
9
|
+
## Result and validation
|
|
10
|
+
|
|
11
|
+
`validation.py` owns the current-code evidence collection names and the rule limiting `planningInputs` to constraints and unknowns. `validators/validate_analysis_report.py` remains the common assembly point for phase semantics, current-code evidence, frozen input snapshots, worker completion and review-resolution checks. Evidence path predicates remain in `analysis_scope.py`.
|
|
12
|
+
|
|
13
|
+
`report.py` builds the impact graph and human view from `changeImpactAnalysis`. The input, Markdown and HTML templates live in `report_assets/`. Common schemas, analysis-review controls and report routing remain shared. The common lead routing policy chooses the next phase; the profile describes facts and constraints for that decision.
|
|
14
|
+
|
|
15
|
+
## Guarantees
|
|
16
|
+
|
|
17
|
+
| ID | Guarantee | Enforcement |
|
|
18
|
+
|---|---|---|
|
|
19
|
+
| CI-1 | Planning inputs contain constraints and unknowns only. | `scripts/okstra_ctl/phases/change_impact_analysis/validation.py::validate_change_impact_semantics` |
|
|
20
|
+
| CI-2 | Impact items require current-code evidence within the included scope. | `validators/validate_analysis_report.py::_validate_all_current_code_evidence`, `scripts/okstra_ctl/phases/change_impact_analysis/tests/test_change_impact_analysis_validation.py::test_change_impact_item_requires_current_code_evidence` |
|
|
21
|
+
| CI-3 | The wizard collects feature evidence before project evidence. | `tests/domain/wizard/test_analysis_inputs.py::test_change_impact_collects_feature_then_project_evidence` |
|
|
22
|
+
| CI-4 | Common validation checks the frozen analysis source, target and evidence snapshot. | `validators/validate_analysis_report.py::validate_analysis_snapshot` |
|
|
23
|
+
|
|
24
|
+
## Tests
|
|
25
|
+
|
|
26
|
+
The phase tests retain the existing impact-evidence and forbidden-planning-field assertions through the common report validator. Shared wizard tests cover selecting, skipping, reusing and editing evidence. Shared schema, report-assembly and rendering tests cover the existing report contract and views.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Change impact content boundaries; analysis evidence stays shared."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
EVIDENCE_COLLECTIONS = (
|
|
5
|
+
"preservedBehaviors",
|
|
6
|
+
"impactItems",
|
|
7
|
+
"dependencyBlastRadius",
|
|
8
|
+
"testImpact",
|
|
9
|
+
"operationalImpact",
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def validate_change_impact_semantics(data: dict, errors: list[str]) -> None:
|
|
14
|
+
analysis = data.get("changeImpactAnalysis") or {}
|
|
15
|
+
allowed = {"constraint", "unknown"}
|
|
16
|
+
for index, row in enumerate(analysis.get("planningInputs") or []):
|
|
17
|
+
if not isinstance(row, dict):
|
|
18
|
+
continue
|
|
19
|
+
for field in sorted(set(row) - allowed):
|
|
20
|
+
errors.append(
|
|
21
|
+
f"changeImpactAnalysis.planningInputs[{index}] forbids `{field}`; "
|
|
22
|
+
"planning inputs may contain constraints and unknowns only"
|
|
23
|
+
)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""오류 분석 단계의 진단 규칙과 자원."""
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"allowed": " - symptom and trigger evidence\n - root-cause hypotheses with supporting citations\n - reproduction gaps and missing observability\n - validation paths to confirm or refute hypotheses\n - one endStateCoverage row per brief end-state id (this phase authors no goal of its own)",
|
|
3
|
+
"forbidden": [
|
|
4
|
+
"source code edits, refactors, or fix attempts",
|
|
5
|
+
"implementation design or planning artifacts",
|
|
6
|
+
"executing builds, migrations, deployments, or any state-mutating command",
|
|
7
|
+
"starting `implementation-planning` or `implementation` inside this run (each must be a separate run, and `implementation` additionally requires an approved `implementation-planning` deliverable)"
|
|
8
|
+
]
|
|
9
|
+
}
|
|
@@ -25,10 +25,10 @@
|
|
|
25
25
|
- **Falsifiable cause candidates:** every root-cause candidate must include supporting evidence, the strongest falsifying evidence checked, confidence, and the next diagnostic action that would disprove it. A candidate that cannot be falsified is too vague for this phase.
|
|
26
26
|
- **Graph-aware scope:** a graph edge can explain ordering or duplication, but it is not proof of cause by itself. Cite code/log evidence before claiming an upstream related task caused the current symptom.
|
|
27
27
|
- **Sharp next diagnostic:** end with the single highest-value diagnostic command, log capture, or file inspection that should happen next, plus the expected signal that would confirm or reject the leading cause.
|
|
28
|
-
- **Fix-design boundary:** do not design the implementation fix beyond what is necessary to validate the cause.
|
|
28
|
+
- **Fix-design boundary:** do not design the implementation fix beyond what is necessary to validate the cause. Record whether the cause is credible and the next diagnostic. The common Phase Routing settles the next phase.
|
|
29
29
|
- Structured diagnosis and routing contract:
|
|
30
30
|
- `errorAnalysis` is the source of truth for reproduction status, `EA-NNN` cause candidates, the sharp next diagnostic, and the next route.
|
|
31
|
-
-
|
|
31
|
+
- Record the route selected by the lead through the common Phase Routing rules, its `routing.leadingCauseId`, and the matching direction.
|
|
32
32
|
- Structure is enforced by `schemas/final-report-v2.0.schema.json` `$defs.ErrorAnalysis`. `validators/validate-run.py::_validate_error_analysis_consistency` enforces the cross-field values: the routing target, a `routing.leadingCauseId` that names an actual `EA-NNN` candidate, the `verdictCard.direction` / `finalVerdict.direction` pair matching that target, and exactly one `phase-continuation` row in `followUpTasks` pointing at it. The prose of `direction` and `nextStep` is not scanned for routing wording.
|
|
33
33
|
- Primary focus areas:
|
|
34
34
|
- symptom and trigger clarification
|