okstra 0.121.1 → 0.123.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 +4 -2
- package/docs/architecture/storage-model.md +14 -0
- package/docs/architecture.md +38 -8
- package/docs/cli.md +45 -5
- package/docs/for-ai/README.md +2 -2
- package/docs/for-ai/skills/okstra-rollup.md +1 -1
- package/docs/for-ai/skills/{okstra-schedule.md → okstra-schedule-gen.md} +3 -3
- package/docs/project-structure-overview.md +4 -3
- package/docs/task-process/implementation-planning.md +33 -9
- package/docs/task-process/implementation.md +21 -2
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/workers/claude-worker.md +1 -1
- package/runtime/bin/lib/okstra/usage.sh +3 -3
- package/runtime/prompts/launch.template.md +9 -6
- package/runtime/prompts/lead/adapters/claude-code.md +104 -0
- package/runtime/prompts/lead/adapters/codex.md +45 -0
- package/runtime/prompts/lead/adapters/external.md +46 -0
- package/runtime/prompts/lead/context-loader.md +5 -5
- package/runtime/prompts/lead/convergence.md +11 -28
- package/runtime/prompts/lead/okstra-lead-contract.md +44 -94
- package/runtime/prompts/lead/plan-body-verification.md +50 -5
- package/runtime/prompts/lead/report-writer.md +59 -54
- package/runtime/prompts/lead/team-contract.md +33 -106
- package/runtime/prompts/profiles/_common-contract.md +3 -3
- package/runtime/prompts/profiles/_implementation-deliverable.md +3 -1
- package/runtime/prompts/profiles/_implementation-executor.md +6 -1
- package/runtime/prompts/profiles/_implementation-verifier.md +2 -2
- package/runtime/prompts/profiles/final-verification.md +2 -0
- package/runtime/prompts/profiles/implementation-planning.md +14 -4
- package/runtime/prompts/profiles/requirements-discovery.md +1 -1
- package/runtime/prompts/wizard/prompts.ko.json +44 -0
- package/runtime/python/okstra_ctl/codex_dispatch.py +23 -1
- package/runtime/python/okstra_ctl/design_prep.py +1462 -0
- package/runtime/python/okstra_ctl/design_surfaces.py +243 -0
- package/runtime/python/okstra_ctl/final_report_schema.py +33 -1
- package/runtime/python/okstra_ctl/implementation_stage.py +35 -0
- package/runtime/python/okstra_ctl/incremental_carry.py +294 -21
- package/runtime/python/okstra_ctl/incremental_scope.py +51 -5
- package/runtime/python/okstra_ctl/lead_runtime.py +4 -0
- package/runtime/python/okstra_ctl/material.py +1 -1
- package/runtime/python/okstra_ctl/model_discovery.py +98 -0
- package/runtime/python/okstra_ctl/models.py +8 -3
- package/runtime/python/okstra_ctl/render.py +81 -194
- package/runtime/python/okstra_ctl/report_views.py +22 -7
- package/runtime/python/okstra_ctl/run.py +53 -5
- package/runtime/python/okstra_ctl/team_reconcile.py +3 -1
- package/runtime/python/okstra_ctl/user_response.py +67 -2
- package/runtime/python/okstra_ctl/wizard.py +283 -3
- package/runtime/python/okstra_token_usage/report.py +11 -0
- package/runtime/schemas/final-report-v1.0.schema.json +339 -0
- package/runtime/skills/okstra-inspect/SKILL.md +2 -2
- package/runtime/skills/okstra-rollup/SKILL.md +1 -1
- package/runtime/skills/{okstra-schedule → okstra-schedule-gen}/SKILL.md +62 -20
- package/runtime/skills/okstra-user-response/SKILL.md +8 -6
- package/runtime/templates/reports/final-report.template.md +71 -4
- package/runtime/templates/reports/i18n/en.json +32 -1
- package/runtime/templates/reports/i18n/ko.json +32 -1
- package/runtime/validators/validate-run.py +515 -5
- package/runtime/validators/validate-schedule.py +11 -7
- package/runtime/validators/validate_session_conformance.py +58 -33
- package/src/cli-registry.mjs +14 -0
- package/src/commands/inspect/design-prep.mjs +23 -0
- package/src/commands/inspect/stage-map.mjs +100 -0
- package/src/commands/lifecycle/install.mjs +3 -0
- package/src/commands/lifecycle/uninstall.mjs +8 -23
- package/src/lib/skill-catalog.mjs +10 -1
|
@@ -37,6 +37,12 @@ from okstra_ctl.models import (
|
|
|
37
37
|
resolve_model_metadata,
|
|
38
38
|
)
|
|
39
39
|
from okstra_ctl.clarification_items import scan_approval_gate, user_response_sidecars
|
|
40
|
+
from okstra_ctl.design_prep import (
|
|
41
|
+
DesignPrepError,
|
|
42
|
+
load_design_prep_items,
|
|
43
|
+
resolve_design_prep,
|
|
44
|
+
write_design_prep_input,
|
|
45
|
+
)
|
|
40
46
|
from okstra_ctl.final_report_paths import final_report_data_path
|
|
41
47
|
from okstra_ctl.pr_template import PrTemplateError, resolve_pr_template_path
|
|
42
48
|
from okstra_ctl.run import (
|
|
@@ -303,6 +309,9 @@ S_BASE_REF_TEXT = "base_ref_text"
|
|
|
303
309
|
S_APPROVED_PLAN_PICK = "approved_plan_pick"
|
|
304
310
|
S_APPROVED_PLAN = "approved_plan"
|
|
305
311
|
S_APPROVE_PLAN_CONFIRM = "approve_plan_confirm"
|
|
312
|
+
S_DESIGN_PREP_DECISION = "design_prep_decision"
|
|
313
|
+
S_DESIGN_PREP_OVERRIDES = "design_prep_overrides"
|
|
314
|
+
S_DESIGN_PREP_CONFIRM = "design_prep_confirm"
|
|
306
315
|
S_STAGE_PICK = "stage_pick"
|
|
307
316
|
S_HANDOFF_STAGE_PICK = "handoff_stage_pick"
|
|
308
317
|
S_EXECUTOR = "executor"
|
|
@@ -403,6 +412,11 @@ class WizardState:
|
|
|
403
412
|
# approve-confirm 단계가 3-옵션(yes_apply/yes/no)으로 확장된다.
|
|
404
413
|
html_approval_sidecar: str = ""
|
|
405
414
|
html_approval_option: str = ""
|
|
415
|
+
design_prep_queue: list[str] = field(default_factory=list)
|
|
416
|
+
design_prep_current: str = ""
|
|
417
|
+
design_prep_decision: str = ""
|
|
418
|
+
design_prep_overrides_json: str = ""
|
|
419
|
+
design_prep_notes: str = ""
|
|
406
420
|
selected_stage: str = "auto"
|
|
407
421
|
selected_stages: str = "" # implementation 다중선택 위상정렬 CSV
|
|
408
422
|
executor: str = ""
|
|
@@ -1077,6 +1091,7 @@ class Step:
|
|
|
1077
1091
|
submit: Callable[[WizardState, str], Optional[str]]
|
|
1078
1092
|
# Field names this step owns; resetting the step clears them.
|
|
1079
1093
|
owns: tuple[str, ...] = ()
|
|
1094
|
+
repeatable: bool = False
|
|
1080
1095
|
|
|
1081
1096
|
|
|
1082
1097
|
def _opt(value: str, label: str = "", description: str = "") -> Option:
|
|
@@ -1939,6 +1954,234 @@ def _submit_approve_plan_confirm(state: WizardState, value: str) -> Optional[str
|
|
|
1939
1954
|
return echo
|
|
1940
1955
|
|
|
1941
1956
|
|
|
1957
|
+
def _design_prep_items_by_id(state: WizardState) -> dict[str, dict[str, Any]]:
|
|
1958
|
+
try:
|
|
1959
|
+
items = load_design_prep_items(Path(state.approved_plan_path))
|
|
1960
|
+
except DesignPrepError as exc:
|
|
1961
|
+
raise WizardError(str(exc)) from exc
|
|
1962
|
+
return {str(item["id"]): item for item in items}
|
|
1963
|
+
|
|
1964
|
+
|
|
1965
|
+
def _ensure_design_prep_queue(state: WizardState) -> None:
|
|
1966
|
+
if state.design_prep_current or state.design_prep_queue:
|
|
1967
|
+
return
|
|
1968
|
+
if S_DESIGN_PREP_DECISION in state.answered:
|
|
1969
|
+
return
|
|
1970
|
+
report_path = Path(state.approved_plan_path)
|
|
1971
|
+
if not re.fullmatch(
|
|
1972
|
+
r"final-report-implementation-planning-\d+\.md",
|
|
1973
|
+
report_path.name,
|
|
1974
|
+
):
|
|
1975
|
+
return
|
|
1976
|
+
data_path = report_path.with_name(
|
|
1977
|
+
report_path.name.removesuffix(".md") + ".data.json"
|
|
1978
|
+
)
|
|
1979
|
+
if not data_path.is_file():
|
|
1980
|
+
return
|
|
1981
|
+
try:
|
|
1982
|
+
items = load_design_prep_items(report_path)
|
|
1983
|
+
effective = resolve_design_prep(report_path)
|
|
1984
|
+
except DesignPrepError as exc:
|
|
1985
|
+
raise WizardError(str(exc)) from exc
|
|
1986
|
+
decisions = {
|
|
1987
|
+
str(item["id"]): item.get("decision")
|
|
1988
|
+
for item in effective.effective_items
|
|
1989
|
+
}
|
|
1990
|
+
unresolved = [
|
|
1991
|
+
item for item in items
|
|
1992
|
+
if item.get("status") in ("blocked", "provisional")
|
|
1993
|
+
and decisions.get(str(item["id"])) == "unanswered"
|
|
1994
|
+
]
|
|
1995
|
+
unresolved.sort(key=lambda item: item.get("status") != "blocked")
|
|
1996
|
+
state.design_prep_queue = [str(item["id"]) for item in unresolved]
|
|
1997
|
+
_advance_design_prep_item(state)
|
|
1998
|
+
|
|
1999
|
+
|
|
2000
|
+
def _advance_design_prep_item(state: WizardState) -> None:
|
|
2001
|
+
state.design_prep_current = (
|
|
2002
|
+
state.design_prep_queue.pop(0) if state.design_prep_queue else ""
|
|
2003
|
+
)
|
|
2004
|
+
state.design_prep_decision = ""
|
|
2005
|
+
state.design_prep_overrides_json = ""
|
|
2006
|
+
state.design_prep_notes = ""
|
|
2007
|
+
|
|
2008
|
+
|
|
2009
|
+
def _current_design_prep_item(state: WizardState) -> dict[str, Any]:
|
|
2010
|
+
item = _design_prep_items_by_id(state).get(state.design_prep_current)
|
|
2011
|
+
if item is None:
|
|
2012
|
+
raise WizardError(
|
|
2013
|
+
f"design preparation item is missing: {state.design_prep_current}"
|
|
2014
|
+
)
|
|
2015
|
+
return item
|
|
2016
|
+
|
|
2017
|
+
|
|
2018
|
+
def _design_prep_decision_applies(state: WizardState) -> bool:
|
|
2019
|
+
if state.task_type != "implementation" or not state.approved_plan_path:
|
|
2020
|
+
return False
|
|
2021
|
+
_ensure_design_prep_queue(state)
|
|
2022
|
+
return bool(state.design_prep_current and not state.design_prep_decision)
|
|
2023
|
+
|
|
2024
|
+
|
|
2025
|
+
def _build_design_prep_decision(state: WizardState) -> Prompt:
|
|
2026
|
+
item = _current_design_prep_item(state)
|
|
2027
|
+
proposal = item.get("aiProposal") or {}
|
|
2028
|
+
t = _p(
|
|
2029
|
+
state.workspace_root,
|
|
2030
|
+
S_DESIGN_PREP_DECISION,
|
|
2031
|
+
item_id=str(item["id"]),
|
|
2032
|
+
title=str(item["title"]),
|
|
2033
|
+
stage_refs=json.dumps(item.get("stageRefs") or [], ensure_ascii=False),
|
|
2034
|
+
proposal_summary=str(proposal.get("summary") or ""),
|
|
2035
|
+
details=json.dumps(proposal.get("details") or [], ensure_ascii=False),
|
|
2036
|
+
assumptions=json.dumps(proposal.get("assumptions") or [], ensure_ascii=False),
|
|
2037
|
+
guardrails=json.dumps(item.get("guardrails") or [], ensure_ascii=False),
|
|
2038
|
+
request_path=str(item.get("requestPath") or ""),
|
|
2039
|
+
)
|
|
2040
|
+
return Prompt(
|
|
2041
|
+
step=S_DESIGN_PREP_DECISION,
|
|
2042
|
+
kind="pick",
|
|
2043
|
+
label=t["label"],
|
|
2044
|
+
options=[_opt(value, label) for value, label in _static_options(t)],
|
|
2045
|
+
echo_template=t["echo_template"],
|
|
2046
|
+
)
|
|
2047
|
+
|
|
2048
|
+
|
|
2049
|
+
def _submit_design_prep_decision(
|
|
2050
|
+
state: WizardState,
|
|
2051
|
+
value: str,
|
|
2052
|
+
) -> Optional[str]:
|
|
2053
|
+
t = _p(state.workspace_root, S_DESIGN_PREP_DECISION,
|
|
2054
|
+
item_id="", title="", stage_refs="", proposal_summary="",
|
|
2055
|
+
details="", assumptions="", guardrails="", request_path="")
|
|
2056
|
+
if value not in ("accept-draft", "modify-draft", "reject-draft", "later"):
|
|
2057
|
+
raise WizardError(t["errors"]["invalid_decision"])
|
|
2058
|
+
if value == "later":
|
|
2059
|
+
item_id = state.design_prep_current
|
|
2060
|
+
_advance_design_prep_item(state)
|
|
2061
|
+
return t["echo_variants"]["later"].format(item_id=item_id)
|
|
2062
|
+
state.design_prep_decision = value
|
|
2063
|
+
state.design_prep_overrides_json = ""
|
|
2064
|
+
state.design_prep_notes = ""
|
|
2065
|
+
return t["echo_template"].format(value=value)
|
|
2066
|
+
|
|
2067
|
+
|
|
2068
|
+
def _design_prep_overrides_applies(state: WizardState) -> bool:
|
|
2069
|
+
if state.design_prep_decision == "modify-draft":
|
|
2070
|
+
return not state.design_prep_overrides_json
|
|
2071
|
+
if state.design_prep_decision == "reject-draft":
|
|
2072
|
+
return not state.design_prep_notes
|
|
2073
|
+
return False
|
|
2074
|
+
|
|
2075
|
+
|
|
2076
|
+
def _build_design_prep_overrides(state: WizardState) -> Prompt:
|
|
2077
|
+
t = _p(
|
|
2078
|
+
state.workspace_root,
|
|
2079
|
+
S_DESIGN_PREP_OVERRIDES,
|
|
2080
|
+
item_id=state.design_prep_current,
|
|
2081
|
+
decision=state.design_prep_decision,
|
|
2082
|
+
)
|
|
2083
|
+
return Prompt(
|
|
2084
|
+
step=S_DESIGN_PREP_OVERRIDES,
|
|
2085
|
+
kind="text",
|
|
2086
|
+
label=t["label"],
|
|
2087
|
+
echo_template=t["echo_template"],
|
|
2088
|
+
)
|
|
2089
|
+
|
|
2090
|
+
|
|
2091
|
+
def _submit_design_prep_overrides(
|
|
2092
|
+
state: WizardState,
|
|
2093
|
+
value: str,
|
|
2094
|
+
) -> Optional[str]:
|
|
2095
|
+
t = _p(state.workspace_root, S_DESIGN_PREP_OVERRIDES,
|
|
2096
|
+
item_id=state.design_prep_current,
|
|
2097
|
+
decision=state.design_prep_decision)
|
|
2098
|
+
if state.design_prep_decision == "reject-draft":
|
|
2099
|
+
if not value.strip():
|
|
2100
|
+
raise WizardError(t["errors"]["note_required"])
|
|
2101
|
+
state.design_prep_notes = value.strip()
|
|
2102
|
+
return t["echo_variants"]["note"]
|
|
2103
|
+
try:
|
|
2104
|
+
overrides = json.loads(value)
|
|
2105
|
+
except json.JSONDecodeError as exc:
|
|
2106
|
+
raise WizardError(t["errors"]["invalid_json"].format(error=exc)) from exc
|
|
2107
|
+
if not isinstance(overrides, dict):
|
|
2108
|
+
raise WizardError(t["errors"]["object_required"])
|
|
2109
|
+
state.design_prep_overrides_json = json.dumps(
|
|
2110
|
+
overrides, ensure_ascii=False, sort_keys=True
|
|
2111
|
+
)
|
|
2112
|
+
return t["echo_variants"]["overrides"]
|
|
2113
|
+
|
|
2114
|
+
|
|
2115
|
+
def _design_prep_confirm_applies(state: WizardState) -> bool:
|
|
2116
|
+
if state.design_prep_decision == "accept-draft":
|
|
2117
|
+
return True
|
|
2118
|
+
if state.design_prep_decision == "modify-draft":
|
|
2119
|
+
return bool(state.design_prep_overrides_json)
|
|
2120
|
+
if state.design_prep_decision == "reject-draft":
|
|
2121
|
+
return bool(state.design_prep_notes)
|
|
2122
|
+
return False
|
|
2123
|
+
|
|
2124
|
+
|
|
2125
|
+
def _build_design_prep_confirm(state: WizardState) -> Prompt:
|
|
2126
|
+
item = _current_design_prep_item(state)
|
|
2127
|
+
t = _p(
|
|
2128
|
+
state.workspace_root,
|
|
2129
|
+
S_DESIGN_PREP_CONFIRM,
|
|
2130
|
+
item_id=state.design_prep_current,
|
|
2131
|
+
decision=state.design_prep_decision,
|
|
2132
|
+
item_snapshot=json.dumps(item, ensure_ascii=False, indent=2, sort_keys=True),
|
|
2133
|
+
overrides=state.design_prep_overrides_json or "{}",
|
|
2134
|
+
notes=state.design_prep_notes or "(none)",
|
|
2135
|
+
)
|
|
2136
|
+
return Prompt(
|
|
2137
|
+
step=S_DESIGN_PREP_CONFIRM,
|
|
2138
|
+
kind="pick",
|
|
2139
|
+
label=t["label"],
|
|
2140
|
+
options=[_opt(value, label) for value, label in _static_options(t)],
|
|
2141
|
+
echo_template=t["echo_template"],
|
|
2142
|
+
)
|
|
2143
|
+
|
|
2144
|
+
|
|
2145
|
+
def _submit_design_prep_confirm(
|
|
2146
|
+
state: WizardState,
|
|
2147
|
+
value: str,
|
|
2148
|
+
) -> Optional[str]:
|
|
2149
|
+
item_id = state.design_prep_current
|
|
2150
|
+
t = _p(state.workspace_root, S_DESIGN_PREP_CONFIRM,
|
|
2151
|
+
item_id=item_id, decision=state.design_prep_decision,
|
|
2152
|
+
item_snapshot="", overrides="", notes="")
|
|
2153
|
+
if value == "no":
|
|
2154
|
+
state.design_prep_decision = ""
|
|
2155
|
+
state.design_prep_overrides_json = ""
|
|
2156
|
+
state.design_prep_notes = ""
|
|
2157
|
+
return t["echo_variants"]["revise"].format(item_id=item_id)
|
|
2158
|
+
if value != "yes":
|
|
2159
|
+
raise WizardError(t["errors"]["confirmation_required"])
|
|
2160
|
+
overrides = json.loads(state.design_prep_overrides_json or "{}")
|
|
2161
|
+
try:
|
|
2162
|
+
path = write_design_prep_input(
|
|
2163
|
+
Path(state.approved_plan_path),
|
|
2164
|
+
item_id,
|
|
2165
|
+
state.design_prep_decision,
|
|
2166
|
+
overrides,
|
|
2167
|
+
state.design_prep_notes,
|
|
2168
|
+
captured_by="okstra-wizard",
|
|
2169
|
+
)
|
|
2170
|
+
except DesignPrepError as exc:
|
|
2171
|
+
raise WizardError(str(exc)) from exc
|
|
2172
|
+
match = re.search(r"-r(\d+)-([0-9a-f-]{36})\.md$", path.name)
|
|
2173
|
+
if match is None:
|
|
2174
|
+
raise WizardError(f"design preparation writer returned invalid path: {path}")
|
|
2175
|
+
echo = t["echo_variants"]["written"].format(
|
|
2176
|
+
item_id=item_id,
|
|
2177
|
+
revision=int(match.group(1)),
|
|
2178
|
+
input_id=match.group(2),
|
|
2179
|
+
path=path,
|
|
2180
|
+
)
|
|
2181
|
+
_advance_design_prep_item(state)
|
|
2182
|
+
return echo
|
|
2183
|
+
|
|
2184
|
+
|
|
1942
2185
|
def _build_stage_pick(state: WizardState) -> Prompt:
|
|
1943
2186
|
"""Parse the Stage Map from the approved plan and build the stage picker."""
|
|
1944
2187
|
t = _p(state.workspace_root, "stage_pick")
|
|
@@ -3116,6 +3359,27 @@ STEPS: list[Step] = [
|
|
|
3116
3359
|
and S_APPROVE_PLAN_CONFIRM not in s.answered),
|
|
3117
3360
|
build=_build_approve_plan_confirm, submit=_submit_approve_plan_confirm,
|
|
3118
3361
|
owns=("approve_plan_candidate",)),
|
|
3362
|
+
Step(S_DESIGN_PREP_DECISION,
|
|
3363
|
+
applies=_design_prep_decision_applies,
|
|
3364
|
+
build=_build_design_prep_decision,
|
|
3365
|
+
submit=_submit_design_prep_decision,
|
|
3366
|
+
owns=("design_prep_queue", "design_prep_current",
|
|
3367
|
+
"design_prep_decision", "design_prep_overrides_json",
|
|
3368
|
+
"design_prep_notes"),
|
|
3369
|
+
repeatable=True),
|
|
3370
|
+
Step(S_DESIGN_PREP_OVERRIDES,
|
|
3371
|
+
applies=_design_prep_overrides_applies,
|
|
3372
|
+
build=_build_design_prep_overrides,
|
|
3373
|
+
submit=_submit_design_prep_overrides,
|
|
3374
|
+
owns=("design_prep_overrides_json", "design_prep_notes"),
|
|
3375
|
+
repeatable=True),
|
|
3376
|
+
Step(S_DESIGN_PREP_CONFIRM,
|
|
3377
|
+
applies=_design_prep_confirm_applies,
|
|
3378
|
+
build=_build_design_prep_confirm,
|
|
3379
|
+
submit=_submit_design_prep_confirm,
|
|
3380
|
+
owns=("design_prep_decision", "design_prep_overrides_json",
|
|
3381
|
+
"design_prep_notes"),
|
|
3382
|
+
repeatable=True),
|
|
3119
3383
|
Step(S_STAGE_PICK,
|
|
3120
3384
|
applies=lambda s: (s.task_type in _STAGE_SCOPED_TASK_TYPES
|
|
3121
3385
|
and bool(s.approved_plan_path)
|
|
@@ -3374,6 +3638,9 @@ _FIELD_DEFAULTS: dict[str, Any] = {
|
|
|
3374
3638
|
"base_ref_pending_text": False, "approved_plan_path": "",
|
|
3375
3639
|
"approved_plan_pending_text": False, "approve_plan_candidate": "",
|
|
3376
3640
|
"html_approval_sidecar": "", "html_approval_option": "",
|
|
3641
|
+
"design_prep_queue": [], "design_prep_current": "",
|
|
3642
|
+
"design_prep_decision": "", "design_prep_overrides_json": "",
|
|
3643
|
+
"design_prep_notes": "",
|
|
3377
3644
|
"selected_stage": "auto",
|
|
3378
3645
|
"selected_stages": "",
|
|
3379
3646
|
"handoff_mode": "", "handoff_stages": "",
|
|
@@ -3424,9 +3691,9 @@ def _build_group_prompt(state: WizardState, group_id: str) -> Prompt:
|
|
|
3424
3691
|
"""
|
|
3425
3692
|
members: list[Prompt] = []
|
|
3426
3693
|
for sid in PROMPT_GROUPS[group_id]:
|
|
3427
|
-
if sid in state.answered:
|
|
3428
|
-
continue
|
|
3429
3694
|
step = STEP_BY_ID[sid]
|
|
3695
|
+
if sid in state.answered and not step.repeatable:
|
|
3696
|
+
continue
|
|
3430
3697
|
if not step.applies(state):
|
|
3431
3698
|
continue
|
|
3432
3699
|
members.append(step.build(state))
|
|
@@ -3445,7 +3712,7 @@ def next_prompt(state: WizardState) -> Prompt:
|
|
|
3445
3712
|
if state.confirmed:
|
|
3446
3713
|
return Prompt(step=S_DONE, kind="done")
|
|
3447
3714
|
for step in STEPS:
|
|
3448
|
-
if step.id in state.answered:
|
|
3715
|
+
if step.id in state.answered and not step.repeatable:
|
|
3449
3716
|
continue
|
|
3450
3717
|
if step.applies(state):
|
|
3451
3718
|
group_id = _STEP_TO_GROUP.get(step.id)
|
|
@@ -3488,6 +3755,19 @@ def _sim_advance(state: WizardState, prompt: Prompt) -> None:
|
|
|
3488
3755
|
if q.step not in state.answered:
|
|
3489
3756
|
state.answered.append(q.step)
|
|
3490
3757
|
return
|
|
3758
|
+
if prompt.step == S_DESIGN_PREP_CONFIRM:
|
|
3759
|
+
_advance_design_prep_item(state)
|
|
3760
|
+
if prompt.step not in state.answered:
|
|
3761
|
+
state.answered.append(prompt.step)
|
|
3762
|
+
return
|
|
3763
|
+
if prompt.step == S_DESIGN_PREP_OVERRIDES:
|
|
3764
|
+
if state.design_prep_decision == "modify-draft":
|
|
3765
|
+
state.design_prep_overrides_json = "{}"
|
|
3766
|
+
else:
|
|
3767
|
+
state.design_prep_notes = "simulation"
|
|
3768
|
+
if prompt.step not in state.answered:
|
|
3769
|
+
state.answered.append(prompt.step)
|
|
3770
|
+
return
|
|
3491
3771
|
STEP_BY_ID[prompt.step].submit(state, _sim_answer(prompt))
|
|
3492
3772
|
if prompt.step not in state.answered:
|
|
3493
3773
|
state.answered.append(prompt.step)
|
|
@@ -14,6 +14,10 @@ _HERE = Path(__file__).resolve().parent.parent
|
|
|
14
14
|
if str(_HERE) not in sys.path:
|
|
15
15
|
sys.path.insert(0, str(_HERE))
|
|
16
16
|
|
|
17
|
+
from okstra_ctl.design_prep import ( # noqa: E402
|
|
18
|
+
DesignPrepError,
|
|
19
|
+
materialize_design_prep_requests,
|
|
20
|
+
)
|
|
17
21
|
from okstra_ctl.final_report_paths import final_report_markdown_path # noqa: E402
|
|
18
22
|
from okstra_ctl.render_final_report import ( # noqa: E402
|
|
19
23
|
FinalReportRenderError,
|
|
@@ -216,4 +220,11 @@ def populate_and_render(
|
|
|
216
220
|
bytes_written = render_to_file(data_path, output_path)
|
|
217
221
|
except FinalReportRenderError as exc:
|
|
218
222
|
raise SubstituteRefusedError(f"render after substitute failed: {exc}") from exc
|
|
223
|
+
if data_path.name.startswith("final-report-implementation-planning-"):
|
|
224
|
+
try:
|
|
225
|
+
materialize_design_prep_requests(data_path)
|
|
226
|
+
except DesignPrepError as exc:
|
|
227
|
+
raise SubstituteRefusedError(
|
|
228
|
+
f"design-prep request materialization failed: {exc}"
|
|
229
|
+
) from exc
|
|
219
230
|
return changes, bytes_written
|