okstra 0.183.2 → 0.184.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 +1 -1
- package/docs/architecture.md +2 -2
- package/docs/cli.md +7 -4
- package/docs/project-structure-overview.md +1 -1
- package/docs/task-process/README.md +1 -1
- package/docs/task-process/common-flow.md +2 -3
- package/docs/task-process/error-analysis.md +3 -4
- package/docs/task-process/final-verification.md +2 -3
- package/docs/task-process/implementation-planning.md +2 -3
- package/docs/task-process/implementation.md +2 -3
- package/docs/task-process/release-handoff.md +3 -4
- package/docs/task-process/requirements-discovery.md +3 -4
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/prompts/launch.template.md +8 -7
- package/runtime/prompts/lead/okstra-lead-contract.md +1 -1
- package/runtime/prompts/lead/plan-body-verification.md +27 -19
- package/runtime/prompts/lead/report-writer.md +3 -3
- package/runtime/prompts/profiles/_implementation-executor.md +1 -0
- package/runtime/prompts/profiles/_implementation-verifier.md +1 -1
- package/runtime/prompts/profiles/implementation-planning.md +9 -11
- package/runtime/prompts/wizard/prompts.ko.json +7 -10
- package/runtime/python/okstra_ctl/conformance.py +37 -1
- package/runtime/python/okstra_ctl/incremental_scope.py +84 -39
- package/runtime/python/okstra_ctl/plan_items.py +410 -1
- package/runtime/python/okstra_ctl/plan_items_cli.py +346 -31
- package/runtime/python/okstra_ctl/render.py +4 -0
- package/runtime/python/okstra_ctl/wizard.py +39 -73
- package/runtime/schemas/final-report-v2.0.schema.json +12 -0
- package/runtime/schemas/final-report-v3.0.schema.json +12 -0
- package/runtime/skills/okstra-run/SKILL.md +2 -2
- package/runtime/validators/validate-run.py +164 -66
- package/runtime/validators/validate_session_conformance.py +24 -5
|
@@ -1231,17 +1231,6 @@ def _wizard_state_from_json(payload: dict[str, Any]) -> WizardState:
|
|
|
1231
1231
|
if version != 2:
|
|
1232
1232
|
state.execution_identity_version = 2
|
|
1233
1233
|
_convert_v1_provider_selections(state, payload)
|
|
1234
|
-
# v1 은 리더 확인 칸이 없었다. 변환 재개 시 다시 묻지 않는다.
|
|
1235
|
-
if (
|
|
1236
|
-
state.host_entry_mode == "current-session"
|
|
1237
|
-
and _LEADER_SESSION_STEP not in state.answered
|
|
1238
|
-
):
|
|
1239
|
-
state.answered.append(_LEADER_SESSION_STEP)
|
|
1240
|
-
if (
|
|
1241
|
-
state.host_entry_mode == "current-session"
|
|
1242
|
-
and _LEADER_SESSION_STEP not in state.role_selection_order
|
|
1243
|
-
):
|
|
1244
|
-
state.role_selection_order.insert(0, _LEADER_SESSION_STEP)
|
|
1245
1234
|
return state
|
|
1246
1235
|
|
|
1247
1236
|
|
|
@@ -1281,13 +1270,8 @@ def _role_model_prompt_id(role: str, ordinal: int) -> str:
|
|
|
1281
1270
|
return f"role-model:{role}:{ordinal}"
|
|
1282
1271
|
|
|
1283
1272
|
|
|
1284
|
-
_LEADER_SESSION_STEP = "leader-session"
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
1273
|
def _is_role_selection_step(step_id: str) -> bool:
|
|
1288
|
-
return step_id
|
|
1289
|
-
("role-count:", "role-model:", "role-add:")
|
|
1290
|
-
)
|
|
1274
|
+
return step_id.startswith(("role-count:", "role-model:", "role-add:"))
|
|
1291
1275
|
|
|
1292
1276
|
|
|
1293
1277
|
def _selected_role_count(
|
|
@@ -1320,36 +1304,6 @@ def _selectable_static_requirements(
|
|
|
1320
1304
|
return tuple(requirements)
|
|
1321
1305
|
|
|
1322
1306
|
|
|
1323
|
-
def _leader_session_prompt(state: WizardState) -> Prompt:
|
|
1324
|
-
"""current-session 리더 칸: 모델/effort 표시만 하고 role_models 에 쓰지 않는다."""
|
|
1325
|
-
attestation = _host_session_context(state).current_model
|
|
1326
|
-
model_ref = (
|
|
1327
|
-
attestation.normalized_model_ref
|
|
1328
|
-
or attestation.observed_model
|
|
1329
|
-
or "current-session"
|
|
1330
|
-
)
|
|
1331
|
-
steps = _load_wizard_root(state.workspace_root)["steps"]
|
|
1332
|
-
raw = steps.get("leader_session") or {}
|
|
1333
|
-
effort_suffix = ""
|
|
1334
|
-
if attestation.effort:
|
|
1335
|
-
effort_template = raw.get("effort_suffix", " · effort {effort}")
|
|
1336
|
-
effort_suffix = effort_template.format(effort=attestation.effort)
|
|
1337
|
-
prompt = _p(
|
|
1338
|
-
state.workspace_root,
|
|
1339
|
-
"leader_session",
|
|
1340
|
-
model_ref=model_ref,
|
|
1341
|
-
effort_suffix=effort_suffix,
|
|
1342
|
-
)
|
|
1343
|
-
continue_label = prompt["options"].get("continue", "계속")
|
|
1344
|
-
return Prompt(
|
|
1345
|
-
step=_LEADER_SESSION_STEP,
|
|
1346
|
-
kind="pick",
|
|
1347
|
-
label=prompt["label"],
|
|
1348
|
-
options=[_opt("continue", continue_label)],
|
|
1349
|
-
echo_template=prompt["echo_template"],
|
|
1350
|
-
)
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
1307
|
def _count_prompt(
|
|
1354
1308
|
state: WizardState,
|
|
1355
1309
|
requirement: RoleRequirement,
|
|
@@ -1777,13 +1731,6 @@ def next_role_prompt(state: WizardState) -> Prompt | None:
|
|
|
1777
1731
|
if not _role_selection_enabled(state) or not _identity_ready(state):
|
|
1778
1732
|
return None
|
|
1779
1733
|
state.use_defaults = False
|
|
1780
|
-
# current-session: 리더는 읽기 전용 확인 칸만 먼저 보여 준다.
|
|
1781
|
-
if (
|
|
1782
|
-
state.host_entry_mode == "current-session"
|
|
1783
|
-
and _LEADER_SESSION_STEP not in state.answered
|
|
1784
|
-
and _LEADER_SESSION_STEP not in state.role_selection_order
|
|
1785
|
-
):
|
|
1786
|
-
return _leader_session_prompt(state)
|
|
1787
1734
|
profile = _load_role_profile_for_state(state)
|
|
1788
1735
|
for requirement in profile.roles:
|
|
1789
1736
|
# 필수 수량: min < max 이고 min > 0 일 때만.
|
|
@@ -1884,11 +1831,6 @@ def _validate_submitted_role_model(
|
|
|
1884
1831
|
|
|
1885
1832
|
|
|
1886
1833
|
def _submit_role_prompt(state: WizardState, prompt: Prompt, value: str) -> str:
|
|
1887
|
-
if prompt.step == _LEADER_SESSION_STEP:
|
|
1888
|
-
if value != "continue":
|
|
1889
|
-
raise WizardError("leader session step accepts only 'continue'")
|
|
1890
|
-
# role_models["leader"] 에 쓰지 않는다 — 현재 세션 모델을 그대로 쓴다.
|
|
1891
|
-
return "leader-session: continue"
|
|
1892
1834
|
if prompt.step.startswith("role-count:"):
|
|
1893
1835
|
role = prompt.step.split(":", 1)[1]
|
|
1894
1836
|
profile = _load_role_profile_for_state(state)
|
|
@@ -2041,9 +1983,11 @@ def _p(workspace_root: str, step_id: str, **vars: str) -> dict:
|
|
|
2041
1983
|
raise WizardError(f"unknown wizard step_id: {step_id!r}")
|
|
2042
1984
|
label_template = raw.get("label", "")
|
|
2043
1985
|
fv_label_template = raw.get("label_final_verification", "")
|
|
1986
|
+
unlinked_template = raw.get("label_unlinked", "")
|
|
2044
1987
|
try:
|
|
2045
1988
|
label = label_template.format(**vars)
|
|
2046
1989
|
label_final_verification = fv_label_template.format(**vars)
|
|
1990
|
+
label_unlinked = unlinked_template.format(**vars)
|
|
2047
1991
|
except KeyError as exc:
|
|
2048
1992
|
missing = exc.args[0] if exc.args else "<unknown>"
|
|
2049
1993
|
raise WizardError(
|
|
@@ -2052,6 +1996,7 @@ def _p(workspace_root: str, step_id: str, **vars: str) -> dict:
|
|
|
2052
1996
|
return {
|
|
2053
1997
|
"label": label,
|
|
2054
1998
|
"label_final_verification": label_final_verification,
|
|
1999
|
+
"label_unlinked": label_unlinked,
|
|
2055
2000
|
"echo_template": raw.get("echo_template", ""),
|
|
2056
2001
|
"options": raw.get("options", {}),
|
|
2057
2002
|
"options_final_verification": raw.get("options_final_verification", {}),
|
|
@@ -4191,14 +4136,15 @@ def _reverify_scope_preview(state: WizardState) -> Optional[dict]:
|
|
|
4191
4136
|
|
|
4192
4137
|
|
|
4193
4138
|
def _reverify_scope_pick_required(state: WizardState) -> bool:
|
|
4194
|
-
"""좁힐 여지가
|
|
4139
|
+
"""좁힐 여지가 있거나, unlinked id 의 stage 번호를 받아야 할 때 묻는다.
|
|
4195
4140
|
|
|
4196
|
-
|
|
4197
|
-
|
|
4198
|
-
확인 블록의 `reverify-scope: full 예상` 줄이 이유까지 같이 알려준다.
|
|
4141
|
+
unlinked 는 full 확정이 아니다. SHA 가 바뀌었거나 입력을 읽을 수 없어
|
|
4142
|
+
`wouldForceFull` 인 경우만 질문이 의미 없다.
|
|
4199
4143
|
"""
|
|
4200
4144
|
preview = _reverify_scope_preview(state)
|
|
4201
|
-
return preview is not None and
|
|
4145
|
+
return preview is not None and (
|
|
4146
|
+
not preview["wouldForceFull"] or bool(preview["unlinkedIds"])
|
|
4147
|
+
)
|
|
4202
4148
|
|
|
4203
4149
|
|
|
4204
4150
|
def _reverify_scope_step_pending(state: WizardState) -> bool:
|
|
@@ -4234,13 +4180,20 @@ def _prior_stage_numbers(state: WizardState) -> set[int]:
|
|
|
4234
4180
|
def _build_reverify_scope_pick(state: WizardState) -> Prompt:
|
|
4235
4181
|
t = _p(state.workspace_root, "reverify_scope_pick")
|
|
4236
4182
|
opts = t["options"]
|
|
4183
|
+
preview = _reverify_scope_preview(state) or {}
|
|
4184
|
+
unlinked = bool(preview.get("unlinkedIds"))
|
|
4185
|
+
options = []
|
|
4186
|
+
if not unlinked:
|
|
4187
|
+
options.append(_opt("auto", opts["auto"]))
|
|
4188
|
+
options.append(_opt("full", opts["full"]))
|
|
4189
|
+
options.append(_opt(PICK_TYPE_CUSTOM, opts[PICK_TYPE_CUSTOM]))
|
|
4190
|
+
else:
|
|
4191
|
+
options.append(_opt(PICK_TYPE_CUSTOM, opts[PICK_TYPE_CUSTOM]))
|
|
4192
|
+
options.append(_opt("full", opts["full"]))
|
|
4193
|
+
label = t["label_unlinked"] if unlinked else t["label"]
|
|
4237
4194
|
return Prompt(
|
|
4238
|
-
step=S_REVERIFY_SCOPE_PICK, kind="pick", label=
|
|
4239
|
-
options=
|
|
4240
|
-
_opt("auto", opts["auto"]),
|
|
4241
|
-
_opt("full", opts["full"]),
|
|
4242
|
-
_opt(PICK_TYPE_CUSTOM, opts[PICK_TYPE_CUSTOM]),
|
|
4243
|
-
],
|
|
4195
|
+
step=S_REVERIFY_SCOPE_PICK, kind="pick", label=label,
|
|
4196
|
+
options=options,
|
|
4244
4197
|
echo_template=t["echo_template"])
|
|
4245
4198
|
|
|
4246
4199
|
|
|
@@ -4255,6 +4208,13 @@ def _submit_reverify_scope_pick(state: WizardState, value: str) -> Optional[str]
|
|
|
4255
4208
|
raise WizardError(
|
|
4256
4209
|
f"expected 'auto' / 'full' / {PICK_TYPE_CUSTOM!r}, got: {value!r}"
|
|
4257
4210
|
)
|
|
4211
|
+
if picked == "auto":
|
|
4212
|
+
preview = _reverify_scope_preview(state) or {}
|
|
4213
|
+
unlinked = preview.get("unlinkedIds") or []
|
|
4214
|
+
if unlinked:
|
|
4215
|
+
raise WizardError(
|
|
4216
|
+
t["errors"]["unlinked_auto"].format(ids=", ".join(unlinked))
|
|
4217
|
+
)
|
|
4258
4218
|
state.reverify_scope = picked
|
|
4259
4219
|
state.reverify_scope_pending_text = False
|
|
4260
4220
|
return t["echo_suffixes"][picked]
|
|
@@ -4271,6 +4231,12 @@ def _submit_reverify_scope_stages(state: WizardState, value: str) -> Optional[st
|
|
|
4271
4231
|
t = _p(state.workspace_root, "reverify_scope_stages")
|
|
4272
4232
|
tokens = [token.strip() for token in value.split(",") if token.strip()]
|
|
4273
4233
|
if not tokens:
|
|
4234
|
+
preview = _reverify_scope_preview(state) or {}
|
|
4235
|
+
unlinked = preview.get("unlinkedIds") or []
|
|
4236
|
+
if unlinked:
|
|
4237
|
+
raise WizardError(
|
|
4238
|
+
t["errors"]["unlinked_empty"].format(ids=", ".join(unlinked))
|
|
4239
|
+
)
|
|
4274
4240
|
state.reverify_scope = "auto"
|
|
4275
4241
|
state.reverify_scope_pending_text = False
|
|
4276
4242
|
return t["echo_suffixes"]["auto"]
|
|
@@ -6341,13 +6307,13 @@ def _reverify_scope_line(state: WizardState) -> Optional[str]:
|
|
|
6341
6307
|
if state.reverify_scope and state.reverify_scope != "auto":
|
|
6342
6308
|
return _msg(state.workspace_root, "confirmation",
|
|
6343
6309
|
"reverify_scope_user_stages", stages=state.reverify_scope)
|
|
6344
|
-
if not preview["wouldForceFull"]:
|
|
6345
|
-
return _msg(state.workspace_root, "confirmation",
|
|
6346
|
-
"reverify_scope_incremental")
|
|
6347
6310
|
if preview["unlinkedIds"]:
|
|
6348
6311
|
return _msg(state.workspace_root, "confirmation",
|
|
6349
6312
|
"reverify_scope_unlinked",
|
|
6350
6313
|
ids=", ".join(preview["unlinkedIds"]))
|
|
6314
|
+
if not preview["wouldForceFull"]:
|
|
6315
|
+
return _msg(state.workspace_root, "confirmation",
|
|
6316
|
+
"reverify_scope_incremental")
|
|
6351
6317
|
return _msg(state.workspace_root, "confirmation", "reverify_scope_full",
|
|
6352
6318
|
reason=preview["reason"])
|
|
6353
6319
|
|
|
@@ -9145,6 +9145,12 @@
|
|
|
9145
9145
|
"properties": {
|
|
9146
9146
|
"setAside": {"type": "array", "items": {"type": "object", "required": ["id", "reason"], "additionalProperties": false, "properties": {"id": {"type": "string", "minLength": 1}, "reason": {"type": "string", "enum": ["record", "observed", "deferred"]}}}},
|
|
9147
9147
|
"stageLedger": {"type": "object", "additionalProperties": {"type": "string", "enum": ["done", "active", "ready", "blocked"]}},
|
|
9148
|
+
"dispatchQueue": {
|
|
9149
|
+
"type": "array",
|
|
9150
|
+
"description": "Plan-item ids sent to verifiers this round. In-scope plus plan-wide; deferred and observed stages are omitted.",
|
|
9151
|
+
"items": {"type": "string", "minLength": 1},
|
|
9152
|
+
"uniqueItems": true
|
|
9153
|
+
},
|
|
9148
9154
|
"uniformVerifiers": {
|
|
9149
9155
|
"type": "array",
|
|
9150
9156
|
"description": "Verifiers whose every vote this round was one verdict. Advisory: a unanimous round is legitimate, but the gate reads as a three-way cross-check unless this sits beside it.",
|
|
@@ -9172,6 +9178,10 @@
|
|
|
9172
9178
|
}
|
|
9173
9179
|
}
|
|
9174
9180
|
},
|
|
9181
|
+
"gating": {
|
|
9182
|
+
"type": "boolean",
|
|
9183
|
+
"description": "If false, plan-body verification is advisory: majority-disagree does not block approval, the self-fix loop does not run, and only one automatic round is allowed. Legal only when designPreparation.mode is no-design-inputs and the Stage Map has exactly one row. Prepare emits true; plan-items prepare/seed flip it after those facts exist."
|
|
9184
|
+
},
|
|
9175
9185
|
"roundCount": {
|
|
9176
9186
|
"type": "integer",
|
|
9177
9187
|
"minimum": 0
|
|
@@ -9247,6 +9257,8 @@
|
|
|
9247
9257
|
"properties": {
|
|
9248
9258
|
"block": {"type": "string", "enum": ["execution", "record"]},
|
|
9249
9259
|
"stageScope": {"type": "array", "minItems": 1, "uniqueItems": true, "items": {"type": "integer", "minimum": 1}},
|
|
9260
|
+
"contentHash": {"type": "string", "pattern": "^[a-f0-9]{64}$", "description": "SHA-256 of subject+payload+block+stageScope for the current extract."},
|
|
9261
|
+
"verifiedContentHash": {"type": "string", "pattern": "^[a-f0-9]{64}$", "description": "contentHash at the last apply-verdicts. Matching hashes keep a pre-self-fix verdict."},
|
|
9250
9262
|
"id": {
|
|
9251
9263
|
"type": "string",
|
|
9252
9264
|
"minLength": 1
|
|
@@ -9140,6 +9140,12 @@
|
|
|
9140
9140
|
"properties": {
|
|
9141
9141
|
"setAside": {"type": "array", "items": {"type": "object", "required": ["id", "reason"], "additionalProperties": false, "properties": {"id": {"type": "string", "minLength": 1}, "reason": {"type": "string", "enum": ["record", "observed", "deferred"]}}}},
|
|
9142
9142
|
"stageLedger": {"type": "object", "additionalProperties": {"type": "string", "enum": ["done", "active", "ready", "blocked"]}},
|
|
9143
|
+
"dispatchQueue": {
|
|
9144
|
+
"type": "array",
|
|
9145
|
+
"description": "Plan-item ids sent to verifiers this round. In-scope plus plan-wide; deferred and observed stages are omitted.",
|
|
9146
|
+
"items": {"type": "string", "minLength": 1},
|
|
9147
|
+
"uniqueItems": true
|
|
9148
|
+
},
|
|
9143
9149
|
"uniformVerifiers": {
|
|
9144
9150
|
"type": "array",
|
|
9145
9151
|
"description": "Verifiers whose every vote this round was one verdict. Advisory: a unanimous round is legitimate, but the gate reads as a three-way cross-check unless this sits beside it.",
|
|
@@ -9167,6 +9173,10 @@
|
|
|
9167
9173
|
}
|
|
9168
9174
|
}
|
|
9169
9175
|
},
|
|
9176
|
+
"gating": {
|
|
9177
|
+
"type": "boolean",
|
|
9178
|
+
"description": "If false, plan-body verification is advisory: majority-disagree does not block approval, the self-fix loop does not run, and only one automatic round is allowed. Legal only when designPreparation.mode is no-design-inputs and the Stage Map has exactly one row. Prepare emits true; plan-items prepare/seed flip it after those facts exist."
|
|
9179
|
+
},
|
|
9170
9180
|
"roundCount": {
|
|
9171
9181
|
"type": "integer",
|
|
9172
9182
|
"minimum": 0
|
|
@@ -9242,6 +9252,8 @@
|
|
|
9242
9252
|
"properties": {
|
|
9243
9253
|
"block": {"type": "string", "enum": ["execution", "record"]},
|
|
9244
9254
|
"stageScope": {"type": "array", "minItems": 1, "uniqueItems": true, "items": {"type": "integer", "minimum": 1}},
|
|
9255
|
+
"contentHash": {"type": "string", "pattern": "^[a-f0-9]{64}$", "description": "SHA-256 of subject+payload+block+stageScope for the current extract."},
|
|
9256
|
+
"verifiedContentHash": {"type": "string", "pattern": "^[a-f0-9]{64}$", "description": "contentHash at the last apply-verdicts. Matching hashes keep a pre-self-fix verdict."},
|
|
9245
9257
|
"id": {
|
|
9246
9258
|
"type": "string",
|
|
9247
9259
|
"minLength": 1
|
|
@@ -185,9 +185,9 @@ That is the entire interactive flow. The wizard handles:
|
|
|
185
185
|
- base-ref pick + git rev-parse validation (skipped when reusing an active worktree),
|
|
186
186
|
- `implementation`-only sub-flow: approved-plan path (frontmatter `approved: true` check) + stage pick (`auto` = the earliest incomplete stage whose dependencies are satisfied, or a specific stage number). Implementer slots use role-count / role-model like every other role (`executor` is only a compatibility alias for `implementer`). When an approved plan is selected and a `## PLAN DECISION` sidecar carrying `Status: approved`, exported from the report — matching the plan on source-report·seq — is detected in that run's sibling `user-responses/`, the approve-confirm step expands to 3 options (`yes_apply` recommended: approve + apply the option as exported / `yes` approve only / `no` abort) — `yes_apply` validates the option against the plan's `optionCandidates` before applying it via the existing approval·option path,
|
|
187
187
|
- `release-handoff`-only sub-flow: after the approved plan auto-resolves, a `handoff_stage_pick` multi-select — choose an eligible stage bundle (stage-group) or the whole task (when an accepted whole-task verification report exists); the result goes out as render-args' `stages` key (csv, empty when whole-task),
|
|
188
|
-
- launch selection after identity/worktree steps:
|
|
188
|
+
- launch selection after identity/worktree steps: role-count per static role (`min..max`, omit uses **recommended**, skip when `min == max`) → role-model `provider/model` per slot → min=0 roles only via role-add (default skip). current-session lead is this session and is listed on the confirmation summary, not as a wizard step. The wizard does not fork on defaults-vs-customize, does not show a provider roster multi-pick, and does not offer a separate implementer-provider pick. Dynamic verifiers are not chosen at launch. `--workers` is compatibility-only, not a launch picker. Repeated `--role-count` / `--role-model` tokens on `renderArgv` are intentional,
|
|
189
189
|
- **resume-clarification (in-session equivalent)** — there is no separate mode or flag matching the shell's `okstra.sh --resume-clarification`; two steps of the standard flow carry out its substance. (1) `reuse_previous` (yes/no to reuse the previous run's settings — in `requirements-discovery` / `error-analysis` / `implementation-planning`, only when prior run-inputs exist): YES prefills role-count·role-model·directive·related-tasks at once. (2) `clarification_pick`: if the **task-type's own** previous `final-report` exists it is auto-recommended as the carry-in input (falling back to the newest by mtime across all phases when absent), and the same run's `user-responses/` sidecar (answers the user filled in) is attached alongside. The chosen path is passed to prepare as `--clarification-response` — the user makes the sidecar via the report's `Export user response`, places it in `runs/<task-type>/user-responses/`, and re-runs the same phase,
|
|
190
|
-
- **re-verification scope (`reverify_scope_pick`, `implementation-planning` clarification re-runs only)** — asked right before `confirm
|
|
190
|
+
- **re-verification scope (`reverify_scope_pick`, `implementation-planning` clarification re-runs only)** — asked right before `confirm` when the re-run is narrowable **or** an answered `C-NNN` traces to no stage. When every answered id traces to a stage: 3 options — `auto` (recommended — leave it to the lead's `okstra incremental-scope` decision) / `full` (re-verify every stage) / Enter directly (a stage-number CSV, validated against the prior report's Stage Map). When an id is unlinked, `auto` is omitted and the user names stages or picks `full`; that unlinked id does not freeze the run at full. The answer goes out as `--reverify-scope` and reaches the lead prompt as the `REVERIFY_SCOPE_MODE` / `REVERIFY_SCOPE_STAGES` tokens; it shapes that CLI's inputs rather than replacing the decision. The confirmation block's `reverify-scope` line names unlinked ids as needing stage numbers, not as a forced full re-run,
|
|
191
191
|
- `release-handoff` PR template override + persist scope,
|
|
192
192
|
- final `Proceed / Edit` confirmation; on `Edit` the wizard asks which step to rewind to and clears every later answer.
|
|
193
193
|
|