okstra 0.172.0 → 0.173.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -6
- package/docs/architecture/storage-model.md +11 -0
- package/docs/architecture.md +16 -14
- package/docs/cli.md +36 -5
- package/docs/performance-improvement-plan-v2.md +6 -5
- package/docs/project-structure-overview.md +21 -13
- package/docs/task-process/README.md +5 -3
- package/docs/task-process/error-analysis.md +2 -2
- package/docs/task-process/final-verification.md +2 -2
- package/docs/task-process/implementation-option-selection.md +70 -0
- package/docs/task-process/implementation-planning.md +23 -15
- package/docs/task-process/requirements-discovery.md +2 -2
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/workers/report-writer-worker.md +30 -6
- package/runtime/bin/lib/okstra/cli.sh +5 -1
- package/runtime/bin/lib/okstra/globals.sh +1 -0
- package/runtime/bin/lib/okstra/usage.sh +3 -0
- package/runtime/bin/okstra.sh +2 -0
- package/runtime/prompts/duties/direction-selection-worker.md +44 -0
- package/runtime/prompts/duties/planning-worker.md +12 -4
- package/runtime/prompts/lead/context-loader.md +1 -1
- package/runtime/prompts/lead/convergence.md +5 -5
- package/runtime/prompts/lead/okstra-lead-contract.md +6 -5
- package/runtime/prompts/lead/plan-body-verification.md +20 -3
- package/runtime/prompts/lead/report-writer.md +27 -5
- package/runtime/prompts/profiles/_common-contract.md +1 -1
- package/runtime/prompts/profiles/_implementation-deliverable.md +2 -2
- package/runtime/prompts/profiles/error-analysis.md +3 -3
- package/runtime/prompts/profiles/final-verification.md +3 -3
- package/runtime/prompts/profiles/forbidden-actions.json +7 -0
- package/runtime/prompts/profiles/implementation-option-selection.md +35 -0
- package/runtime/prompts/profiles/implementation-planning.md +50 -38
- package/runtime/prompts/profiles/implementation.md +2 -1
- package/runtime/prompts/profiles/improvement-discovery.md +1 -1
- package/runtime/prompts/profiles/requirements-discovery.md +3 -3
- package/runtime/prompts/wizard/prompts.ko.json +9 -1
- package/runtime/python/okstra_ctl/agent_invocation.py +1 -0
- package/runtime/python/okstra_ctl/analysis_packet.py +6 -0
- package/runtime/python/okstra_ctl/exact_coverage.py +128 -0
- package/runtime/python/okstra_ctl/fix_cycles.py +3 -1
- package/runtime/python/okstra_ctl/implementation_direction.py +836 -0
- package/runtime/python/okstra_ctl/implementation_options.py +479 -0
- package/runtime/python/okstra_ctl/plan_items.py +51 -3
- package/runtime/python/okstra_ctl/render.py +1 -0
- package/runtime/python/okstra_ctl/render_final_report.py +1 -0
- package/runtime/python/okstra_ctl/report_contract.py +45 -13
- package/runtime/python/okstra_ctl/report_html/render.py +4 -2
- package/runtime/python/okstra_ctl/report_html/router.py +4 -0
- package/runtime/python/okstra_ctl/report_html/view_models/implementation_option_selection.py +32 -0
- package/runtime/python/okstra_ctl/report_html/view_models/implementation_planning.py +25 -10
- package/runtime/python/okstra_ctl/report_views.py +148 -12
- package/runtime/python/okstra_ctl/run.py +350 -2
- package/runtime/python/okstra_ctl/scope_provenance.py +15 -9
- package/runtime/python/okstra_ctl/user_response.py +75 -0
- package/runtime/python/okstra_ctl/wizard.py +144 -0
- package/runtime/python/okstra_ctl/worker_prompt_policy.py +2 -0
- package/runtime/python/okstra_ctl/workflow.py +29 -7
- package/runtime/schemas/final-report-v2.0.schema.json +1428 -137
- package/runtime/templates/reports/final-report-v2.template.md +4 -0
- package/runtime/templates/reports/final-verification-input.template.md +1 -1
- package/runtime/templates/reports/html/base.template.html +3 -2
- package/runtime/templates/reports/html/i18n/en.json +21 -1
- package/runtime/templates/reports/html/i18n/ko.json +21 -1
- package/runtime/templates/reports/html/macros/forms.html +21 -2
- package/runtime/templates/reports/html/tasks/implementation-option-selection.template.html +49 -0
- package/runtime/templates/reports/html/tasks/implementation-planning.template.html +36 -2
- package/runtime/templates/reports/i18n/en.json +13 -0
- package/runtime/templates/reports/implementation-input.template.md +4 -2
- package/runtime/templates/reports/implementation-planning-input.template.md +18 -4
- package/runtime/templates/reports/improvement-discovery-input.template.md +1 -1
- package/runtime/templates/reports/md/tasks/implementation-option-selection.template.md +13 -0
- package/runtime/templates/reports/md/tasks/implementation-planning.template.md +17 -0
- package/runtime/templates/reports/report.js +111 -4
- package/runtime/templates/reports/task-brief.template.md +9 -3
- package/runtime/templates/reports/user-response.template.md +25 -4
- package/runtime/templates/worker-prompt-preamble.md +8 -0
- package/runtime/validators/validate-implementation-plan-stages.py +106 -1
- package/runtime/validators/validate-report-views.py +2 -2
- package/runtime/validators/validate-run.py +135 -25
- package/runtime/validators/validate_improvement_report.py +5 -1
- package/src/commands/execute/codex-run.mjs +1 -0
- package/src/commands/execute/render-bundle.mjs +1 -0
|
@@ -82,6 +82,11 @@ from okstra_ctl.design_prep import (
|
|
|
82
82
|
resolve_design_prep,
|
|
83
83
|
write_design_prep_input,
|
|
84
84
|
)
|
|
85
|
+
from okstra_ctl.implementation_direction import (
|
|
86
|
+
DirectionSelectionError,
|
|
87
|
+
lexical_absolute_path,
|
|
88
|
+
validate_task_artifact_path,
|
|
89
|
+
)
|
|
85
90
|
from okstra_ctl.final_report_paths import final_report_data_path
|
|
86
91
|
from okstra_ctl.plan_run_root import list_implementation_planning_reports
|
|
87
92
|
from okstra_ctl.pr_template import PrTemplateError, resolve_pr_template_path
|
|
@@ -154,6 +159,7 @@ TASK_TYPES: list[tuple[str, str]] = [
|
|
|
154
159
|
("improvement-discovery", "Find improvement candidates within a codebase scope and lens whitelist"),
|
|
155
160
|
*zip(ANALYSIS_TASK_TYPES, _ANALYSIS_TASK_TYPE_DESCRIPTIONS),
|
|
156
161
|
("error-analysis", "Evidence-based root-cause analysis (no code changes)"),
|
|
162
|
+
("implementation-option-selection", "Compare implementation options (read-only)"),
|
|
157
163
|
("implementation-planning", "Plan options + request user approval"),
|
|
158
164
|
("implementation", "Execute approved plan (requires approved final-report)"),
|
|
159
165
|
("final-verification", "Acceptance + residual-risk review"),
|
|
@@ -331,6 +337,7 @@ S_ANALYSIS_TARGET_PICK = "analysis_target_pick"
|
|
|
331
337
|
S_ANALYSIS_TARGET = "analysis_target"
|
|
332
338
|
S_BASE_REF_PICK = "base_ref_pick"
|
|
333
339
|
S_BASE_REF_TEXT = "base_ref_text"
|
|
340
|
+
S_SELECTED_DIRECTION_PICK = "selected_direction_pick"
|
|
334
341
|
S_APPROVED_PLAN_PICK = "approved_plan_pick"
|
|
335
342
|
S_APPROVED_PLAN = "approved_plan"
|
|
336
343
|
S_APPROVE_PLAN_CONFIRM = "approve_plan_confirm"
|
|
@@ -495,6 +502,7 @@ class WizardState:
|
|
|
495
502
|
clarification_response_path: str = ""
|
|
496
503
|
clarification_pending_text: bool = False
|
|
497
504
|
last_final_report_cached: str = ""
|
|
505
|
+
selected_direction_path: str = ""
|
|
498
506
|
# "" | "auto" | "full" | "<stage csv>" — 사용자가 고른 이번 재실행의 재검증
|
|
499
507
|
# 범위. implementation-planning 재실행에서 좁힐 여지가 있을 때만 채워진다.
|
|
500
508
|
reverify_scope: str = ""
|
|
@@ -595,6 +603,105 @@ def _require_file(path_str: str, project_root: Path, label: str) -> Path:
|
|
|
595
603
|
return p
|
|
596
604
|
|
|
597
605
|
|
|
606
|
+
_SELECTION_REPORT_RE = re.compile(
|
|
607
|
+
r"^final-report-implementation-option-selection-(?P<seq>\d{3,})\.md$"
|
|
608
|
+
)
|
|
609
|
+
|
|
610
|
+
|
|
611
|
+
def _selected_direction_candidates(state: WizardState) -> list[str]:
|
|
612
|
+
if not state.project_root or not state.task_group or not state.task_id:
|
|
613
|
+
return []
|
|
614
|
+
project_root = Path(state.project_root).resolve()
|
|
615
|
+
task_root = lexical_absolute_path(
|
|
616
|
+
task_dir(project_root, state.task_group, state.task_id)
|
|
617
|
+
)
|
|
618
|
+
reports = (
|
|
619
|
+
task_runs_dir(project_root, state.task_group, state.task_id)
|
|
620
|
+
/ "implementation-option-selection"
|
|
621
|
+
/ "reports"
|
|
622
|
+
)
|
|
623
|
+
candidates: list[tuple[int, Path]] = []
|
|
624
|
+
for report in reports.glob("final-report-implementation-option-selection-*.md"):
|
|
625
|
+
match = _SELECTION_REPORT_RE.fullmatch(report.name)
|
|
626
|
+
if match is None:
|
|
627
|
+
continue
|
|
628
|
+
try:
|
|
629
|
+
validated = validate_task_artifact_path(
|
|
630
|
+
report, task_root, "selection report"
|
|
631
|
+
)
|
|
632
|
+
except DirectionSelectionError:
|
|
633
|
+
continue
|
|
634
|
+
candidates.append((int(match.group("seq")), validated))
|
|
635
|
+
return [
|
|
636
|
+
_project_relative_path(path, project_root)
|
|
637
|
+
for _, path in sorted(candidates, reverse=True)[:3]
|
|
638
|
+
]
|
|
639
|
+
|
|
640
|
+
|
|
641
|
+
def _planning_rerun_selected(state: WizardState) -> bool:
|
|
642
|
+
if (
|
|
643
|
+
not state.clarification_response_path
|
|
644
|
+
or not state.project_root
|
|
645
|
+
or not state.task_group
|
|
646
|
+
or not state.task_id
|
|
647
|
+
):
|
|
648
|
+
return False
|
|
649
|
+
project_root = Path(state.project_root).resolve()
|
|
650
|
+
raw_path = Path(state.clarification_response_path).expanduser()
|
|
651
|
+
path = lexical_absolute_path(
|
|
652
|
+
raw_path if raw_path.is_absolute() else project_root / raw_path
|
|
653
|
+
)
|
|
654
|
+
task_root = lexical_absolute_path(
|
|
655
|
+
task_dir(project_root, state.task_group, state.task_id)
|
|
656
|
+
)
|
|
657
|
+
reports = lexical_absolute_path(
|
|
658
|
+
task_runs_dir(project_root, state.task_group, state.task_id)
|
|
659
|
+
/ "implementation-planning"
|
|
660
|
+
/ "reports"
|
|
661
|
+
)
|
|
662
|
+
try:
|
|
663
|
+
validate_task_artifact_path(path, task_root, "planning report")
|
|
664
|
+
except DirectionSelectionError:
|
|
665
|
+
return False
|
|
666
|
+
return (
|
|
667
|
+
path.is_file()
|
|
668
|
+
and not path.is_symlink()
|
|
669
|
+
and re.fullmatch(
|
|
670
|
+
r"final-report-implementation-planning-\d{3,}\.md", path.name
|
|
671
|
+
)
|
|
672
|
+
is not None
|
|
673
|
+
and path.parent == reports
|
|
674
|
+
)
|
|
675
|
+
|
|
676
|
+
|
|
677
|
+
def _build_selected_direction_pick(state: WizardState) -> Prompt:
|
|
678
|
+
candidates = _selected_direction_candidates(state)
|
|
679
|
+
t = _p(state.workspace_root, S_SELECTED_DIRECTION_PICK)
|
|
680
|
+
if not candidates:
|
|
681
|
+
raise WizardError(t["errors"]["none"])
|
|
682
|
+
return Prompt(
|
|
683
|
+
step=S_SELECTED_DIRECTION_PICK,
|
|
684
|
+
kind="pick",
|
|
685
|
+
label=t["label"],
|
|
686
|
+
options=[_opt(path, path) for path in candidates],
|
|
687
|
+
echo_template=t["echo_template"],
|
|
688
|
+
)
|
|
689
|
+
|
|
690
|
+
|
|
691
|
+
def _submit_selected_direction_pick(
|
|
692
|
+
state: WizardState, value: str
|
|
693
|
+
) -> Optional[str]:
|
|
694
|
+
candidates = _selected_direction_candidates(state)
|
|
695
|
+
if value not in candidates:
|
|
696
|
+
raise WizardError(
|
|
697
|
+
_p(state.workspace_root, S_SELECTED_DIRECTION_PICK)["errors"][
|
|
698
|
+
"unknown"
|
|
699
|
+
].format(value=value)
|
|
700
|
+
)
|
|
701
|
+
state.selected_direction_path = value
|
|
702
|
+
return f"selected-direction: {value}"
|
|
703
|
+
|
|
704
|
+
|
|
598
705
|
def _data_json_approved_state(plan_path: Path) -> Optional[bool]:
|
|
599
706
|
"""`approved` flag of the sibling final-report data.json (the SSOT).
|
|
600
707
|
|
|
@@ -621,6 +728,18 @@ def _classify_approved_plan(path_str: str, project_root: Path) -> tuple[Path, bo
|
|
|
621
728
|
``already_fully_approved=False`` — the approve-confirm step offers to flip it.
|
|
622
729
|
"""
|
|
623
730
|
p = _require_file(path_str, project_root, "approved plan")
|
|
731
|
+
loaded = _load_final_report_data_if_present(p)
|
|
732
|
+
if loaded is not None:
|
|
733
|
+
planning = loaded[1].get("implementationPlanning")
|
|
734
|
+
if (
|
|
735
|
+
isinstance(planning, dict)
|
|
736
|
+
and planning.get("planningContract") == "selected-direction"
|
|
737
|
+
and planning.get("outcome") == "direction-invalidated"
|
|
738
|
+
):
|
|
739
|
+
raise WizardError(
|
|
740
|
+
"direction-invalidated planning reports are not approvable; "
|
|
741
|
+
"re-enter implementation-option-selection"
|
|
742
|
+
)
|
|
624
743
|
body = p.read_text(encoding="utf-8", errors="replace")
|
|
625
744
|
frontmatter = _extract_frontmatter_block(body)
|
|
626
745
|
if frontmatter is None:
|
|
@@ -690,6 +809,14 @@ def _find_html_approval_sidecar(
|
|
|
690
809
|
승인이 아닌 판정(반려·재작업 요청)은 여기서 걸러진다 — 이 단계가 묻는 것은
|
|
691
810
|
"사용자가 이 plan 을 승인해 두었는가" 뿐이고, 반려 사유는 다음 planning
|
|
692
811
|
run 이 sidecar 를 통째로 읽어 처리한다."""
|
|
812
|
+
loaded = _load_final_report_data_if_present(plan_path)
|
|
813
|
+
if loaded is not None:
|
|
814
|
+
planning = loaded[1].get("implementationPlanning")
|
|
815
|
+
if (
|
|
816
|
+
isinstance(planning, dict)
|
|
817
|
+
and planning.get("planningContract") == "selected-direction"
|
|
818
|
+
):
|
|
819
|
+
return None
|
|
693
820
|
responses_dir = plan_path.parent.parent / "user-responses"
|
|
694
821
|
if not responses_dir.is_dir():
|
|
695
822
|
return None
|
|
@@ -4175,6 +4302,19 @@ STEPS: list[Step] = [
|
|
|
4175
4302
|
applies=lambda s: s.base_ref_pending_text,
|
|
4176
4303
|
build=_build_base_ref_text, submit=_submit_base_ref_text,
|
|
4177
4304
|
owns=("base_ref", "base_ref_pending_text")),
|
|
4305
|
+
Step(S_SELECTED_DIRECTION_PICK,
|
|
4306
|
+
applies=lambda s: (
|
|
4307
|
+
s.task_type == "implementation-planning"
|
|
4308
|
+
and not s.selected_direction_path
|
|
4309
|
+
and not _planning_rerun_selected(s)
|
|
4310
|
+
and _brief_resolved(s)
|
|
4311
|
+
and _base_ref_ready(s)
|
|
4312
|
+
and not s.base_ref_pending_text
|
|
4313
|
+
and S_SELECTED_DIRECTION_PICK not in s.answered
|
|
4314
|
+
),
|
|
4315
|
+
build=_build_selected_direction_pick,
|
|
4316
|
+
submit=_submit_selected_direction_pick,
|
|
4317
|
+
owns=("selected_direction_path",)),
|
|
4178
4318
|
Step(S_APPROVED_PLAN_PICK,
|
|
4179
4319
|
applies=lambda s: (s.task_type in _STAGE_SCOPED_TASK_TYPES
|
|
4180
4320
|
and not s.approved_plan_path
|
|
@@ -4556,6 +4696,7 @@ _FIELD_DEFAULTS: dict[str, Any] = {
|
|
|
4556
4696
|
"directive_pending_text": False,
|
|
4557
4697
|
"related_tasks_raw": "", "related_tasks_pending_text": False,
|
|
4558
4698
|
"clarification_response_path": "", "clarification_pending_text": False,
|
|
4699
|
+
"selected_direction_path": "",
|
|
4559
4700
|
"reverify_scope": "", "reverify_scope_pending_text": False,
|
|
4560
4701
|
"pr_template_path": "", "pr_template_pending_text": False,
|
|
4561
4702
|
"pr_template_scope": "",
|
|
@@ -4924,6 +5065,7 @@ def render_args(state: WizardState) -> dict[str, str]:
|
|
|
4924
5065
|
"report-writer-model": state.report_writer_model,
|
|
4925
5066
|
"related-tasks": state.related_tasks_raw,
|
|
4926
5067
|
"clarification-response": state.clarification_response_path,
|
|
5068
|
+
"selected-direction": state.selected_direction_path,
|
|
4927
5069
|
"reverify-scope": (
|
|
4928
5070
|
state.reverify_scope
|
|
4929
5071
|
if state.task_type == "implementation-planning" else ""
|
|
@@ -5080,6 +5222,8 @@ def confirmation_block(state: WizardState) -> str:
|
|
|
5080
5222
|
reverify_line = _reverify_scope_line(state)
|
|
5081
5223
|
if reverify_line is not None:
|
|
5082
5224
|
lines.append(reverify_line)
|
|
5225
|
+
if state.selected_direction_path:
|
|
5226
|
+
lines.append(f" selected-direction: {state.selected_direction_path}")
|
|
5083
5227
|
if state.task_type == "release-handoff" and state.handoff_mode:
|
|
5084
5228
|
scope = (
|
|
5085
5229
|
_msg(state.workspace_root, "confirmation",
|
|
@@ -51,6 +51,7 @@ FINAL_VERIFICATION_HEADERS = (
|
|
|
51
51
|
SUPPORTED_TASK_TYPES = frozenset({
|
|
52
52
|
"requirements-discovery",
|
|
53
53
|
"error-analysis",
|
|
54
|
+
"implementation-option-selection",
|
|
54
55
|
"implementation-planning",
|
|
55
56
|
"improvement-discovery",
|
|
56
57
|
"implementation",
|
|
@@ -67,6 +68,7 @@ ANALYSIS_DUTY_BY_TASK_TYPE: dict[str, AgentAudience] = {
|
|
|
67
68
|
"requirements-discovery": "discovery-worker",
|
|
68
69
|
"improvement-discovery": "discovery-worker",
|
|
69
70
|
"error-analysis": "diagnosis-worker",
|
|
71
|
+
"implementation-option-selection": "direction-selection-worker",
|
|
70
72
|
"implementation-planning": "planning-worker",
|
|
71
73
|
}
|
|
72
74
|
WORKER_PREAMBLE_FILENAME_BY_AUDIENCE = {
|
|
@@ -17,22 +17,34 @@ from okstra_ctl.analysis_inputs import ANALYSIS_TASK_TYPES
|
|
|
17
17
|
PHASE_SEQUENCE = [
|
|
18
18
|
"requirements-discovery",
|
|
19
19
|
"error-analysis",
|
|
20
|
+
"implementation-option-selection",
|
|
20
21
|
"implementation-planning",
|
|
21
22
|
"implementation",
|
|
22
23
|
"final-verification",
|
|
23
24
|
"release-handoff",
|
|
24
25
|
]
|
|
25
26
|
|
|
27
|
+
REQUIREMENTS_DISCOVERY_ROUTING_TARGETS = frozenset(
|
|
28
|
+
{"error-analysis", "implementation-option-selection"}
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
ERROR_ANALYSIS_ROUTING_DIRECTIONS = {
|
|
32
|
+
"error-analysis": "continue-investigation",
|
|
33
|
+
"implementation-option-selection": "begin-option-selection",
|
|
34
|
+
}
|
|
35
|
+
|
|
26
36
|
DEFAULT_NEXT_PHASE = {
|
|
27
|
-
"requirements-discovery": "
|
|
37
|
+
"requirements-discovery": "implementation-option-selection",
|
|
28
38
|
"improvement-discovery": "pending-routing-decision",
|
|
29
39
|
**{task_type: "pending-routing-decision" for task_type in ANALYSIS_TASK_TYPES},
|
|
30
|
-
"error-analysis": "implementation-
|
|
40
|
+
"error-analysis": "implementation-option-selection",
|
|
41
|
+
"implementation-option-selection": "implementation-planning",
|
|
31
42
|
"implementation-planning": "implementation",
|
|
32
43
|
"implementation": "final-verification",
|
|
33
44
|
# final-verification 의 다음 단계는 verdict 에 따라 갈리므로 정적 매핑은
|
|
34
45
|
# `pending-release-handoff` 로 둔다 (accepted 일 때만 release-handoff 로
|
|
35
|
-
# 진입; 그 외에는 error-analysis / implementation-
|
|
46
|
+
# 진입; 그 외에는 error-analysis / implementation-option-selection /
|
|
47
|
+
# implementation-planning 으로 리라우팅).
|
|
36
48
|
"final-verification": "pending-release-handoff",
|
|
37
49
|
"release-handoff": "done-or-follow-up",
|
|
38
50
|
}
|
|
@@ -87,12 +99,22 @@ PHASE_RULES: dict[str, dict[str, str]] = {
|
|
|
87
99
|
" - one endStateCoverage row per brief end-state id (this phase authors no goal of its own)"
|
|
88
100
|
),
|
|
89
101
|
},
|
|
102
|
+
"implementation-option-selection": {
|
|
103
|
+
"allowed": (
|
|
104
|
+
" - candidate-comparison evidence for up to three options per worker\n"
|
|
105
|
+
" - preselected-validation that re-evaluates the merged candidate set\n"
|
|
106
|
+
" - a ranked display of at most three options with requirement mappings\n"
|
|
107
|
+
" - an audit record for every rejected candidate\n"
|
|
108
|
+
" - one endStateCoverage row per brief end-state id (this phase authors no goal of its own)"
|
|
109
|
+
),
|
|
110
|
+
},
|
|
90
111
|
"implementation-planning": {
|
|
91
112
|
"allowed": (
|
|
92
113
|
" - pre-planning context exploration notes (files/interfaces inspected, recent commits scanned, ambiguities flagged)\n"
|
|
93
|
-
" -
|
|
94
|
-
" -
|
|
95
|
-
" -
|
|
114
|
+
" - selected-direction planning: realize the one validated selected direction without reopening candidate comparison, with a File Structure list (Create/Modify/Delete with one-line responsibility per file), affected interfaces, and blast-radius estimate\n"
|
|
115
|
+
" - legacy candidate-comparison planning only: at least two implementation option candidates, each with a File Structure list (Create/Modify/Delete with one-line responsibility per file), affected interfaces, and blast-radius estimate\n"
|
|
116
|
+
" - legacy candidate-comparison planning only: a trade-off matrix across options (complexity, risk, reversibility, test cost, rollout cost) and recommended option with rationale tied to isolation / single-responsibility / YAGNI principles\n"
|
|
117
|
+
" - bite-sized stepwise execution order for the selected direction or legacy recommended option (each step ~2-5 min, exact file paths and commands, TDD ordering when applicable, no placeholders)\n"
|
|
96
118
|
" - dependency / migration risk assessment, validation checklist (pre / mid / post with exact commands), rollback strategy with revert path and trigger signal\n"
|
|
97
119
|
" - every unresolved ambiguity registered as a `Blocks=approval` row in the `## 1. Clarification Items` table (do NOT create a separate `Open Questions` block under `5.5.x` — the unified table is the single home)\n"
|
|
98
120
|
" - YAML frontmatter line `approved: false` awaiting human flip to `true`\n"
|
|
@@ -117,7 +139,7 @@ PHASE_RULES: dict[str, dict[str, str]] = {
|
|
|
117
139
|
"allowed": (
|
|
118
140
|
" - acceptance verdict with requirement coverage assessment\n"
|
|
119
141
|
" - residual risk and regression notes\n"
|
|
120
|
-
" - recommended follow-up routing (`error-analysis` / `implementation-planning` / `release-handoff`) for any defects detected"
|
|
142
|
+
" - recommended follow-up routing (`error-analysis` / `implementation-option-selection` / `implementation-planning` / `release-handoff`) for any defects detected"
|
|
121
143
|
),
|
|
122
144
|
},
|
|
123
145
|
"release-handoff": {
|