okstra 0.175.1 → 0.176.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/docs/architecture/storage-model.md +2 -2
- package/docs/architecture.md +22 -19
- package/docs/cli.md +18 -14
- package/docs/for-ai/skills/okstra-inspect.md +2 -3
- package/docs/for-ai/skills/okstra-rollup.md +1 -0
- package/docs/project-structure-overview.md +57 -56
- package/docs/task-process/README.md +11 -9
- package/docs/task-process/common-flow.md +13 -16
- package/docs/task-process/error-analysis.md +9 -10
- package/docs/task-process/final-verification.md +7 -7
- package/docs/task-process/implementation-planning.md +9 -9
- package/docs/task-process/implementation.md +6 -6
- package/docs/task-process/release-handoff.md +8 -7
- package/docs/task-process/requirements-discovery.md +8 -8
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/bin/lib/okstra/interactive.sh +12 -6
- package/runtime/bin/lib/okstra/usage.sh +3 -2
- package/runtime/bin/okstra-spawn-followups.py +4 -2
- package/runtime/prompts/launch.template.md +2 -2
- package/runtime/prompts/lead/context-loader.md +1 -2
- package/runtime/prompts/lead/okstra-lead-contract.md +3 -4
- package/runtime/prompts/lead/report-writer.md +15 -1
- package/runtime/prompts/lead/team-contract.md +16 -12
- package/runtime/prompts/profiles/_implementation-deliverable.md +1 -1
- package/runtime/prompts/profiles/_implementation-executor.md +0 -3
- package/runtime/prompts/profiles/_implementation-verifier.md +3 -7
- package/runtime/prompts/profiles/change-impact-analysis.md +9 -5
- package/runtime/prompts/profiles/error-analysis.md +14 -8
- package/runtime/prompts/profiles/feature-analysis.md +9 -5
- package/runtime/prompts/profiles/final-verification.md +10 -7
- package/runtime/prompts/profiles/implementation-option-selection.md +9 -5
- package/runtime/prompts/profiles/implementation-planning.md +13 -7
- package/runtime/prompts/profiles/implementation.md +9 -5
- package/runtime/prompts/profiles/improvement-discovery.md +10 -6
- package/runtime/prompts/profiles/project-analysis.md +9 -5
- package/runtime/prompts/profiles/requirements-discovery.md +15 -9
- package/runtime/prompts/wizard/prompts.ko.json +18 -1
- package/runtime/python/okstra_ctl/adapters/runtime/__init__.py +1 -0
- package/runtime/python/okstra_ctl/adapters/runtime/assembly.py +27 -0
- package/runtime/python/okstra_ctl/adapters/runtime/cli_wrapper.py +49 -0
- package/runtime/python/okstra_ctl/adapters/runtime/cmux.py +74 -0
- package/runtime/python/okstra_ctl/application/open_worker.py +29 -0
- package/runtime/python/okstra_ctl/assignment_resolver.py +27 -27
- package/runtime/python/okstra_ctl/dispatch_core.py +120 -100
- package/runtime/python/okstra_ctl/domain/host.py +3 -1
- package/runtime/python/okstra_ctl/domain/wizard/interaction.py +5 -0
- package/runtime/python/okstra_ctl/domain/worker_runtime.py +44 -0
- package/runtime/python/okstra_ctl/implementation_outcome.py +21 -5
- package/runtime/python/okstra_ctl/legacy_model_selection.py +115 -27
- package/runtime/python/okstra_ctl/manager_sync.py +4 -1
- package/runtime/python/okstra_ctl/next_phase.py +236 -0
- package/runtime/python/okstra_ctl/ports/worker_runtime.py +26 -0
- package/runtime/python/okstra_ctl/recap.py +4 -1
- package/runtime/python/okstra_ctl/render.py +46 -47
- package/runtime/python/okstra_ctl/role_requirements.py +28 -35
- package/runtime/python/okstra_ctl/rollup.py +4 -1
- package/runtime/python/okstra_ctl/run.py +8 -5
- package/runtime/python/okstra_ctl/stage_fix_carry.py +17 -1
- package/runtime/python/okstra_ctl/team.py +14 -18
- package/runtime/python/okstra_ctl/wizard.py +248 -56
- package/runtime/python/okstra_ctl/worker_prompt_body.py +18 -2
- package/runtime/python/okstra_ctl/worker_prompt_contract.py +40 -9
- package/runtime/python/okstra_ctl/worker_prompt_headers.py +16 -1
- package/runtime/python/okstra_ctl/workflow.py +18 -32
- package/runtime/python/okstra_ctl/worktree.py +3 -3
- package/runtime/python/okstra_ctl/worktree_registry.py +5 -4
- package/runtime/python/okstra_project/state.py +54 -6
- package/runtime/schemas/final-report-v2.0.schema.json +43 -5
- package/runtime/skills/okstra-inspect/facets/recap.md +2 -0
- package/runtime/skills/okstra-inspect/facets/report.md +1 -1
- package/runtime/skills/okstra-inspect/facets/status.md +15 -13
- package/runtime/skills/okstra-rollup/SKILL.md +1 -0
- package/runtime/skills/okstra-run/SKILL.md +5 -5
- package/runtime/templates/implementation-worker-preamble.md +3 -18
- package/runtime/templates/project-docs/task-index.template.md +0 -1
- package/runtime/templates/reports/html/macros/forms.html +5 -4
- package/runtime/templates/reports/html/tasks/final-verification.template.html +1 -1
- package/runtime/templates/reports/html/tasks/implementation.template.html +1 -1
- package/runtime/templates/worker-prompt-preamble.md +3 -36
- package/runtime/validators/validate-run.py +57 -99
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
"""Phase / workflow state computation.
|
|
2
2
|
|
|
3
|
-
bash `compute_workflow_render_context`
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
bash `compute_workflow_render_context` 의 python 구현. (task-type, current
|
|
4
|
+
task/run status, render-only flag) 만 받아서 WORKFLOW_* 필드 dict 를 돌려준다.
|
|
5
|
+
|
|
6
|
+
다음 phase 는 이 모듈이 계산하지 않는다. 포인터의 shape·승격·투영은
|
|
7
|
+
`okstra_ctl.next_phase` 하나가 소유하고, 값은 리포트가 저작한 라우팅에서
|
|
8
|
+
투영된다.
|
|
6
9
|
|
|
7
10
|
task-type 별 PHASE_ALLOWED_OUTPUTS 본문 매핑은 모듈 상수(`PHASE_RULES`)로 보유하고,
|
|
8
11
|
PHASE_FORBIDDEN_ACTIONS 는 `prompts/profiles/forbidden-actions.json` SSOT 에서 로드한다.
|
|
@@ -12,8 +15,6 @@ from __future__ import annotations
|
|
|
12
15
|
import json
|
|
13
16
|
from pathlib import Path
|
|
14
17
|
|
|
15
|
-
from okstra_ctl.analysis_inputs import ANALYSIS_TASK_TYPES
|
|
16
|
-
|
|
17
18
|
PHASE_SEQUENCE = [
|
|
18
19
|
"requirements-discovery",
|
|
19
20
|
"error-analysis",
|
|
@@ -33,22 +34,6 @@ ERROR_ANALYSIS_ROUTING_DIRECTIONS = {
|
|
|
33
34
|
"implementation-option-selection": "begin-option-selection",
|
|
34
35
|
}
|
|
35
36
|
|
|
36
|
-
DEFAULT_NEXT_PHASE = {
|
|
37
|
-
"requirements-discovery": "implementation-option-selection",
|
|
38
|
-
"improvement-discovery": "pending-routing-decision",
|
|
39
|
-
**{task_type: "pending-routing-decision" for task_type in ANALYSIS_TASK_TYPES},
|
|
40
|
-
"error-analysis": "implementation-option-selection",
|
|
41
|
-
"implementation-option-selection": "implementation-planning",
|
|
42
|
-
"implementation-planning": "implementation",
|
|
43
|
-
"implementation": "final-verification",
|
|
44
|
-
# final-verification 의 다음 단계는 verdict 에 따라 갈리므로 정적 매핑은
|
|
45
|
-
# `pending-release-handoff` 로 둔다 (accepted 일 때만 release-handoff 로
|
|
46
|
-
# 진입; 그 외에는 error-analysis / implementation-option-selection /
|
|
47
|
-
# implementation-planning 으로 리라우팅).
|
|
48
|
-
"final-verification": "pending-release-handoff",
|
|
49
|
-
"release-handoff": "done-or-follow-up",
|
|
50
|
-
}
|
|
51
|
-
|
|
52
37
|
# Phase 별 allowed outputs. bash heredoc 원문 그대로 옮긴 값 — 들여쓰기와 백틱은
|
|
53
38
|
# prompt template 에 그대로 박혀 lead 가 읽는다. forbidden actions 는 이 dict 가
|
|
54
39
|
# 아니라 prompts/profiles/forbidden-actions.json (load_phase_forbidden) 이 SSOT.
|
|
@@ -56,7 +41,7 @@ PHASE_RULES: dict[str, dict[str, str]] = {
|
|
|
56
41
|
"requirements-discovery": {
|
|
57
42
|
"allowed": (
|
|
58
43
|
" - work-category classification (bugfix / feature / refactor / ops / improvement)\n"
|
|
59
|
-
" - routing decision
|
|
44
|
+
" - routing decision as the `routing` object `{nextTaskType, readyWhen, rationale}`: `nextTaskType` is `error-analysis` or `implementation-option-selection` and nothing else — this phase never hands off directly to planning or implementation. Prose alone does not route the task — Phase 7 projects `workflow.nextRecommendedPhase` from `nextTaskType`\n"
|
|
60
45
|
" - missing-input list and clarification requests\n"
|
|
61
46
|
" - approval / confirmation checkpoints recorded for the next phase\n"
|
|
62
47
|
" - one endStateCoverage row per brief end-state id (this phase authors no goal of its own)"
|
|
@@ -132,14 +117,14 @@ PHASE_RULES: dict[str, dict[str, str]] = {
|
|
|
132
117
|
" - TDD evidence for TDD-applicable steps: failing-test output before implementation commit and passing-test output after, with framing SHAs\n"
|
|
133
118
|
" - per-verifier sections for every verifier in the resolved roster, with an independent verdict (PASS / CONCERNS / FAIL) and cited diff snippets; dissent is preserved by the Okstra lead\n"
|
|
134
119
|
" - rollback verification (advisory, never blocks — record the revert path for a human; `result` is ok / not-applicable / advisory — human-run)\n"
|
|
135
|
-
" - routing recommendation
|
|
120
|
+
" - routing recommendation as the `routingRecommendation` object `{target, rationale}`: `target` is one of `final-verification`, `error-analysis`, `implementation-planning`, `implementation`, and `rationale` is why that one and nothing else. Prose alone does not route the task — Phase 7 projects `workflow.nextRecommendedPhase` from `target`"
|
|
136
121
|
),
|
|
137
122
|
},
|
|
138
123
|
"final-verification": {
|
|
139
124
|
"allowed": (
|
|
140
125
|
" - acceptance verdict with requirement coverage assessment\n"
|
|
141
126
|
" - residual risk and regression notes\n"
|
|
142
|
-
" -
|
|
127
|
+
" - routing recommendation as the `routingRecommendation` object `{target, rationale}`: `target` is one of `release-handoff`, `release-handoff(stage-group)`, `error-analysis`, `implementation-option-selection`, `implementation-planning`, `implementation`, `done`, and `rationale` ties that choice to the verdict and the blocker list. Both `release-handoff` forms require the `accepted` verdict; plain `release-handoff` additionally requires `whole-task` verification scope. Prose alone does not route the task — Phase 7 projects `workflow.nextRecommendedPhase` from `target`"
|
|
143
128
|
),
|
|
144
129
|
},
|
|
145
130
|
"release-handoff": {
|
|
@@ -187,10 +172,6 @@ def load_phase_forbidden(workspace_root: Path) -> dict[str, str]:
|
|
|
187
172
|
return {phase: render_forbidden(items) for phase, items in data.items()}
|
|
188
173
|
|
|
189
174
|
|
|
190
|
-
def default_next_phase_for(task_type: str) -> str:
|
|
191
|
-
return DEFAULT_NEXT_PHASE.get(task_type, "unknown")
|
|
192
|
-
|
|
193
|
-
|
|
194
175
|
def compute_workflow_state(
|
|
195
176
|
*,
|
|
196
177
|
task_type: str,
|
|
@@ -221,8 +202,6 @@ def compute_workflow_state(
|
|
|
221
202
|
else:
|
|
222
203
|
phase_state = "not-started"
|
|
223
204
|
|
|
224
|
-
routing_status = "pending" if task_type == "requirements-discovery" else "not-applicable"
|
|
225
|
-
|
|
226
205
|
if render_only:
|
|
227
206
|
checkpoint_label = "instruction-set-prepared"
|
|
228
207
|
elif current_run_status == "in-progress":
|
|
@@ -239,10 +218,17 @@ def compute_workflow_state(
|
|
|
239
218
|
"WORKFLOW_WORK_CATEGORY": resolved_work_category,
|
|
240
219
|
"WORKFLOW_CURRENT_PHASE": task_type,
|
|
241
220
|
"WORKFLOW_CURRENT_PHASE_STATE": phase_state,
|
|
242
|
-
|
|
221
|
+
# 값이 아니라 자리표시자다. 실제 포인터는 리포트 라우팅에서 투영돼
|
|
222
|
+
# 매니페스트의 `workflow.nextRecommendedPhase` 구조체로 저장된다.
|
|
223
|
+
# 이 ctx 값은 그 구조체로 대체되지 않는다 — 남은 소비자는
|
|
224
|
+
# `render.py` 의 `_active_workflow` 하나이고, 그것은 이 빈 문자열을
|
|
225
|
+
# 승격해 phase 없는 pending 포인터를 active-run-context 에 적는다.
|
|
226
|
+
# `prompts/launch.template.md` 는 더 이상 이 토큰을 쓰지 않는다.
|
|
227
|
+
# 다음 phase 이름을 문장에 끼워 넣던 두 줄이, 이름 없이 읽히는 문장으로
|
|
228
|
+
# 바뀌었기 때문이다.
|
|
229
|
+
"WORKFLOW_NEXT_RECOMMENDED_PHASE": "",
|
|
243
230
|
"WORKFLOW_LAST_COMPLETED_PHASE": last_completed,
|
|
244
231
|
"WORKFLOW_AWAITING_APPROVAL": "false",
|
|
245
|
-
"WORKFLOW_ROUTING_STATUS": routing_status,
|
|
246
232
|
"WORKFLOW_LAST_SAFE_CHECKPOINT_LABEL": checkpoint_label,
|
|
247
233
|
"PHASE_ALLOWED_OUTPUTS": rules["allowed"],
|
|
248
234
|
"PHASE_FORBIDDEN_ACTIONS": forbidden_by_phase.get(
|
|
@@ -4,9 +4,9 @@ Every okstra task — regardless of task-type/phase — runs inside an
|
|
|
4
4
|
isolated git worktree rooted at
|
|
5
5
|
`~/.okstra/worktrees/<project_id>/<task_group>/<task_id>/`. The same
|
|
6
6
|
worktree is reused across all phases of one task-key (requirements-
|
|
7
|
-
discovery → error-analysis → implementation-
|
|
8
|
-
so phase N picks up exactly
|
|
9
|
-
behind.
|
|
7
|
+
discovery → error-analysis → implementation-option-selection →
|
|
8
|
+
implementation-planning → implementation), so phase N picks up exactly
|
|
9
|
+
the working-tree state phase N-1 left behind.
|
|
10
10
|
|
|
11
11
|
A global registry (`worktree_registry.py`) maps task-keys to the
|
|
12
12
|
on-disk path + branch and serialises reservations, so two concurrent
|
|
@@ -8,10 +8,11 @@ processes cannot race to reserve the same path or branch.
|
|
|
8
8
|
|
|
9
9
|
Why a global registry:
|
|
10
10
|
- A single task-key spans multiple phases (requirements-discovery →
|
|
11
|
-
error-analysis → implementation-
|
|
12
|
-
phases must land in
|
|
13
|
-
|
|
14
|
-
creating a
|
|
11
|
+
error-analysis → implementation-option-selection →
|
|
12
|
+
implementation-planning → implementation). All phases must land in
|
|
13
|
+
the **same** worktree on the **same** branch. Re-entry from any
|
|
14
|
+
phase must look up the existing entry instead of creating a
|
|
15
|
+
duplicate.
|
|
15
16
|
- Two different task-keys must never collide on the same branch name.
|
|
16
17
|
A global branch index makes that detectable cheaply.
|
|
17
18
|
- Cleanup of stale entries (worktree dir removed manually) needs a
|
|
@@ -53,6 +53,20 @@ def _load_json(path: Path) -> Optional[dict]:
|
|
|
53
53
|
raise StateError(f"failed to parse {path}: {exc}") from exc
|
|
54
54
|
|
|
55
55
|
|
|
56
|
+
def _next_phase():
|
|
57
|
+
"""`okstra_ctl.next_phase` 를 호출 시점에 가져온다.
|
|
58
|
+
|
|
59
|
+
모듈 최상단에서 import 하면 순환이 된다 — `okstra_ctl/__init__.py` 가
|
|
60
|
+
`.ids` 를 거쳐 이 모듈의 `slugify` 를 도로 import 하기 때문에, 여기서
|
|
61
|
+
okstra_ctl 을 먼저 끌어오면 아직 정의되지 않은 이름을 찾다가 ImportError 로
|
|
62
|
+
죽는다. 같은 파일의 `_reconcile_task_root_best_effort` 도 같은 이유로 지연
|
|
63
|
+
import 를 쓴다.
|
|
64
|
+
"""
|
|
65
|
+
from okstra_ctl import next_phase
|
|
66
|
+
|
|
67
|
+
return next_phase
|
|
68
|
+
|
|
69
|
+
|
|
56
70
|
def slugify(value: str) -> str:
|
|
57
71
|
"""task-group / task-id segment 를 디스크 경로용 slug 로 정규화한다.
|
|
58
72
|
|
|
@@ -83,8 +97,32 @@ def read_latest_task(project_root: Path) -> Optional[dict]:
|
|
|
83
97
|
|
|
84
98
|
|
|
85
99
|
def read_task_manifest(task_root: Path) -> Optional[dict]:
|
|
86
|
-
"""<task-root>/task-manifest.json 을 dict 로. 파일 없으면 None.
|
|
87
|
-
|
|
100
|
+
"""<task-root>/task-manifest.json 을 dict 로. 파일 없으면 None.
|
|
101
|
+
|
|
102
|
+
구형 문자열 포인터는 반환값에서 승격한다. 읽는 곳이 여럿이므로 승격을 seam
|
|
103
|
+
하나에 두지 않으면 형태가 갈라진다 (ADR-0004).
|
|
104
|
+
|
|
105
|
+
**디스크는 건드리지 않는다.** 승격한 값을 여기서 되적으면 lock 없는
|
|
106
|
+
read-modify-write 가 되어, 파싱과 쓰기 사이에 다른 프로세스가 적은
|
|
107
|
+
`currentPhase` · `phaseStates` · `latestReportPath` 를 되돌린다
|
|
108
|
+
(`os.replace` 는 찢어진 읽기만 막고 갱신 유실은 못 막는다). 이 분기는
|
|
109
|
+
디스크 포인터가 아직 문자열일 때 — 즉 업그레이드 직후 진행 중인 task 가
|
|
110
|
+
살아 있을 때 — 만 발화하므로 되돌릴 수 있는 값이 바로 phase 진행 상태다.
|
|
111
|
+
그래서 디스크 형태는 다음 writer 가 손댈 때까지 구형으로 남는다. 모든
|
|
112
|
+
읽기가 여기서 승격하므로 소비자는 그 차이를 보지 않는다.
|
|
113
|
+
"""
|
|
114
|
+
manifest = _load_json(Path(task_root) / TASK_MANIFEST_FILENAME)
|
|
115
|
+
if not isinstance(manifest, dict):
|
|
116
|
+
return manifest
|
|
117
|
+
workflow = manifest.get("workflow")
|
|
118
|
+
if not isinstance(workflow, dict):
|
|
119
|
+
return manifest
|
|
120
|
+
workflow["nextRecommendedPhase"] = _next_phase().promote(
|
|
121
|
+
workflow.get("nextRecommendedPhase")
|
|
122
|
+
)
|
|
123
|
+
# routingStatus 는 포인터 status 로 흡수됐다 — 승격과 같은 자리에서 지운다.
|
|
124
|
+
workflow.pop("routingStatus", None)
|
|
125
|
+
return manifest
|
|
88
126
|
|
|
89
127
|
|
|
90
128
|
def read_task_key(task_root: Path) -> str:
|
|
@@ -146,11 +184,18 @@ def _entry_with_manifest_state(entry: dict, task_root: Path) -> dict:
|
|
|
146
184
|
("currentPhase", "currentPhase"),
|
|
147
185
|
("currentPhaseState", "currentPhaseState"),
|
|
148
186
|
("lastCompletedPhase", "lastCompletedPhase"),
|
|
149
|
-
("nextRecommendedPhase", "nextRecommendedPhase"),
|
|
150
187
|
):
|
|
151
188
|
value = workflow.get(source)
|
|
152
189
|
if isinstance(value, str):
|
|
153
190
|
out[target] = value
|
|
191
|
+
# 포인터는 구조체라 위 루프의 str 검사에 걸리지 않는다 — 그 검사에 남겨두면
|
|
192
|
+
# 값이 예외도 로그도 없이 버려진다. 매니페스트가 값을 가질 때만 카탈로그
|
|
193
|
+
# 항목을 덮는 규칙은 위 세 필드와 같게 유지하고, 덮지 않을 때는 카탈로그에
|
|
194
|
+
# 남아 있던 구형 문자열을 승격해 내보낸다.
|
|
195
|
+
raw = workflow.get("nextRecommendedPhase")
|
|
196
|
+
if raw is None:
|
|
197
|
+
raw = entry.get("nextRecommendedPhase")
|
|
198
|
+
out["nextRecommendedPhase"] = _next_phase().promote(raw)
|
|
154
199
|
out.update(derived_task_status(manifest))
|
|
155
200
|
return out
|
|
156
201
|
|
|
@@ -334,7 +379,9 @@ def resolve_task_identity(project_root: Path, task_key: str) -> dict:
|
|
|
334
379
|
"taskType": manifest.get("taskType") or "",
|
|
335
380
|
"currentPhase": workflow.get("currentPhase") or "",
|
|
336
381
|
"currentPhaseState": workflow.get("currentPhaseState") or "",
|
|
337
|
-
"nextRecommendedPhase":
|
|
382
|
+
"nextRecommendedPhase": _next_phase().promote(
|
|
383
|
+
workflow.get("nextRecommendedPhase")
|
|
384
|
+
),
|
|
338
385
|
"lastCompletedPhase": workflow.get("lastCompletedPhase") or "",
|
|
339
386
|
"awaitingApproval": bool(workflow.get("awaitingApproval", False)),
|
|
340
387
|
"taskBriefPath": manifest.get("taskBriefPath") or "",
|
|
@@ -363,8 +410,9 @@ def task_read_side_snapshot(project_root: Path, task_key: str) -> dict:
|
|
|
363
410
|
"currentPhase": workflow.get("currentPhase"),
|
|
364
411
|
"currentPhaseState": workflow.get("currentPhaseState"),
|
|
365
412
|
"lastCompletedPhase": workflow.get("lastCompletedPhase"),
|
|
366
|
-
"nextRecommendedPhase":
|
|
367
|
-
|
|
413
|
+
"nextRecommendedPhase": _next_phase().promote(
|
|
414
|
+
workflow.get("nextRecommendedPhase")
|
|
415
|
+
),
|
|
368
416
|
"phaseStates": workflow.get("phaseStates"),
|
|
369
417
|
},
|
|
370
418
|
"status": derived_task_status(manifest),
|
|
@@ -1375,8 +1375,26 @@
|
|
|
1375
1375
|
}
|
|
1376
1376
|
},
|
|
1377
1377
|
"routingRecommendation": {
|
|
1378
|
-
"type": "
|
|
1379
|
-
"
|
|
1378
|
+
"type": "object",
|
|
1379
|
+
"additionalProperties": false,
|
|
1380
|
+
"required": [
|
|
1381
|
+
"target",
|
|
1382
|
+
"rationale"
|
|
1383
|
+
],
|
|
1384
|
+
"properties": {
|
|
1385
|
+
"target": {
|
|
1386
|
+
"enum": [
|
|
1387
|
+
"final-verification",
|
|
1388
|
+
"error-analysis",
|
|
1389
|
+
"implementation-planning",
|
|
1390
|
+
"implementation"
|
|
1391
|
+
]
|
|
1392
|
+
},
|
|
1393
|
+
"rationale": {
|
|
1394
|
+
"type": "string",
|
|
1395
|
+
"minLength": 1
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1380
1398
|
},
|
|
1381
1399
|
"userNarrative": {
|
|
1382
1400
|
"$ref": "#/$defs/ImplementationUserNarrative"
|
|
@@ -1528,9 +1546,29 @@
|
|
|
1528
1546
|
}
|
|
1529
1547
|
},
|
|
1530
1548
|
"routingRecommendation": {
|
|
1531
|
-
"type": "
|
|
1532
|
-
"
|
|
1533
|
-
"
|
|
1549
|
+
"type": "object",
|
|
1550
|
+
"additionalProperties": false,
|
|
1551
|
+
"required": [
|
|
1552
|
+
"target",
|
|
1553
|
+
"rationale"
|
|
1554
|
+
],
|
|
1555
|
+
"properties": {
|
|
1556
|
+
"target": {
|
|
1557
|
+
"enum": [
|
|
1558
|
+
"release-handoff",
|
|
1559
|
+
"release-handoff(stage-group)",
|
|
1560
|
+
"error-analysis",
|
|
1561
|
+
"implementation-option-selection",
|
|
1562
|
+
"implementation-planning",
|
|
1563
|
+
"implementation",
|
|
1564
|
+
"done"
|
|
1565
|
+
]
|
|
1566
|
+
},
|
|
1567
|
+
"rationale": {
|
|
1568
|
+
"type": "string",
|
|
1569
|
+
"minLength": 1
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1534
1572
|
},
|
|
1535
1573
|
"userNarrative": {
|
|
1536
1574
|
"$ref": "#/$defs/FinalVerificationUserNarrative"
|
|
@@ -24,6 +24,8 @@ okstra recap assemble <resolved-target> --project-root <projectRoot>
|
|
|
24
24
|
|
|
25
25
|
Parse the stdout JSON (`{taskKey, runCount, transitions[], latestPhaseStates}`) and narrate the per-run before/after transitions. For each `transitions[]` entry, show `fromPhase → toPhase` (previous run currentPhase → this run currentPhase), `status`, `lastCompletedPhase`, `nextRecommendedPhase`, and `reportPath` in chronological order. If `runCount == 0`, answer only "This task has no recorded runs." and do not claim to have read any file.
|
|
26
26
|
|
|
27
|
+
`nextRecommendedPhase` in each entry is an **object** `{phase, status, rationale}`, not a phase-name string. Narrate it as `phase` when `phase` is non-empty, and as what `status` says otherwise — `pending` (that run did not settle the route), `blocked` (it stopped on something outside the run), `terminal` (the lifecycle ended there). Printing the object itself puts a raw dict in front of the reader.
|
|
28
|
+
|
|
27
29
|
For a run that has a `reportPath`, read that final-report to enrich the summary only when the user wants a deeper one (do not read them all automatically).
|
|
28
30
|
|
|
29
31
|
### recap.3 — Free-form Q&A loop
|
|
@@ -27,7 +27,7 @@ D. **Specific task-type (fallback):** `latestReportPath` is task-type-agnostic.
|
|
|
27
27
|
1. Verify `latestReportPath` is non-empty AND the file exists on disk. Either signal indicates report presence (tolerant).
|
|
28
28
|
2. If present, display the path and ask the user whether to read it.
|
|
29
29
|
3. If absent, check `task-manifest.json` signals:
|
|
30
|
-
- `latestReportPath` empty/missing AND `currentStatus != completed` AND `workStatus != done` AND `workflow.
|
|
30
|
+
- `latestReportPath` empty/missing AND `currentStatus != completed` AND `workStatus != done` AND `workflow.nextRecommendedPhase.status != "terminal"` → `This task is not yet complete (currentStatus: <currentStatus>, workStatus: <workStatus>).`
|
|
31
31
|
- Any signal indicates completion but the file is missing → `Report file does not exist: <path>`
|
|
32
32
|
|
|
33
33
|
`workStatus` enum: `todo | in-progress | blocked | done`. `currentStatus`: `completed` / `contract-violated` etc. `"completed"` string does NOT exist in `workStatus` — do not confuse the two.
|
|
@@ -18,8 +18,7 @@ Read `.okstra/discovery/task-catalog.json`. The catalog is the authoritative sou
|
|
|
18
18
|
| `currentStatus` | task-level status |
|
|
19
19
|
| `currentPhase` | lifecycle current phase |
|
|
20
20
|
| `currentPhaseState` | lifecycle phase state |
|
|
21
|
-
| `nextRecommendedPhase` | next
|
|
22
|
-
| `routingStatus` | routing decision status |
|
|
21
|
+
| `nextRecommendedPhase` | next-phase pointer — an **object** `{phase, status, rationale}`, never a string. `status` is `ready` / `pending` / `blocked` / `terminal`. The lead writes `phase` only under `ready`, but that is an authoring rule, not a constraint the struct enforces — `prepare` lowers a `ready` pointer to `pending` and keeps its `phase`, so a non-`ready` pointer that still names a phase is a normal state to read. Decide launchability from `status` alone. Render `phase` (or `--` when it is empty) — never the object itself. |
|
|
23
22
|
| `awaitingApproval` | whether awaiting approval |
|
|
24
23
|
| `latestRunStatus` | latest run status |
|
|
25
24
|
| `latestReportPath` | latest report path |
|
|
@@ -29,9 +28,9 @@ Read `.okstra/discovery/task-catalog.json`. The catalog is the authoritative sou
|
|
|
29
28
|
|
|
30
29
|
Sort by `updatedAt` desc, then `taskKey`.
|
|
31
30
|
|
|
32
|
-
The overview table is intentionally narrow so it renders cleanly in a terminal. Only six columns are shown; for any task that needs a closer look (phase state,
|
|
31
|
+
The overview table is intentionally narrow so it renders cleanly in a terminal. Only six columns are shown; for any task that needs a closer look (phase state, the pointer's status and rationale, approval gate, last run status, resume path, etc.) tell the user to run `/okstra-inspect status <task-key>` for the detail view in `status.2`.
|
|
33
32
|
|
|
34
|
-
If `awaitingApproval` is true OR `
|
|
33
|
+
The `Next` cell is `nextRecommendedPhase.phase`, or `--` when that string is empty. If `awaitingApproval` is true OR `nextRecommendedPhase.status` is anything other than `ready`, append a `*` to the cell and explain the marker once below the table — an unset or non-`ready` pointer means no next run is launchable yet.
|
|
35
34
|
|
|
36
35
|
```markdown
|
|
37
36
|
## okstra Status — <project-id>
|
|
@@ -39,9 +38,9 @@ If `awaitingApproval` is true OR `routingStatus == "pending"`, append a `*` to t
|
|
|
39
38
|
| # | Task Key | Category | Phase | workStatus | Next |
|
|
40
39
|
|---|----------|----------|-------|------------|------|
|
|
41
40
|
| 1 | proj:group:id | bugfix | error-analysis | in-progress | implementation-planning |
|
|
42
|
-
| 2 | proj:group:id2 | feature | requirements-discovery | done |
|
|
41
|
+
| 2 | proj:group:id2 | feature | requirements-discovery | done | --* |
|
|
43
42
|
|
|
44
|
-
`*` = awaiting user approval or
|
|
43
|
+
`*` = awaiting user approval, or the next-phase pointer is not `ready`. Run `/okstra-inspect status <task-key>` for details.
|
|
45
44
|
```
|
|
46
45
|
|
|
47
46
|
### status.2 — Specific task status
|
|
@@ -52,7 +51,9 @@ Given a specific `task-key` or `task-group + task-id`:
|
|
|
52
51
|
2. If necessary, read `.okstra/tasks/<task-group>/<task-id>/task-manifest.json` directly.
|
|
53
52
|
3. If you need the latest run information, read `history/timeline.json` along with the latest run manifest.
|
|
54
53
|
|
|
55
|
-
Required fields: `taskKey`, `taskType`, `workCategory`, `currentStatus`, `latestRunStatus`, `workflow.{currentPhase, currentPhaseState, phaseStates, lastCompletedPhase, nextRecommendedPhase, awaitingApproval,
|
|
54
|
+
Required fields: `taskKey`, `taskType`, `workCategory`, `currentStatus`, `latestRunStatus`, `workflow.{currentPhase, currentPhaseState, phaseStates, lastCompletedPhase, nextRecommendedPhase, awaitingApproval, lastSafeCheckpoint}`, `workStatus`, `workStatusUpdatedAt`, `workStatusNote`, `latestReportPath`, `latestResumeCommandPath`, `historyTimelinePath`.
|
|
55
|
+
|
|
56
|
+
`workflow.nextRecommendedPhase` is the `{phase, status, rationale}` object described in `status.1` — read its three fields, do not print it whole. A `task-manifest.json` written before the object existed still holds a bare string there; `scripts/okstra_ctl/next_phase.py::promote` is what turns that string into the object, and every CLI-projected surface you can read instead (`task-catalog.json`, `okstra rollup`, `okstra recap`) has already applied it. Prefer those over a raw manifest rather than promoting a legacy string yourself.
|
|
56
57
|
|
|
57
58
|
```markdown
|
|
58
59
|
## okstra Task Status — <task-key>
|
|
@@ -61,9 +62,8 @@ Required fields: `taskKey`, `taskType`, `workCategory`, `currentStatus`, `latest
|
|
|
61
62
|
- Current phase: `<phase>`
|
|
62
63
|
- Current phase state: `<phase-state>`
|
|
63
64
|
- Last completed phase: `<phase-or-->`
|
|
64
|
-
- Next
|
|
65
|
+
- Next phase pointer: `<phase-or-->` (`<status>`) — `<rationale-or-->`
|
|
65
66
|
- Awaiting approval: `<yes|no>`
|
|
66
|
-
- Routing status: `<routing-status>`
|
|
67
67
|
- Task status: `<task-status>`
|
|
68
68
|
- Latest run status: `<run-status>`
|
|
69
69
|
- Latest report: `<relative-path-or-->`
|
|
@@ -95,9 +95,11 @@ The status response always includes one of:
|
|
|
95
95
|
|
|
96
96
|
1. **Resume current run** — if `latestResumeCommandPath` exists, display that path.
|
|
97
97
|
2. **Restart current phase** — task can be re-run with the same `task-key` and current `taskType`.
|
|
98
|
-
3
|
|
99
|
-
|
|
100
|
-
|
|
98
|
+
Branches 3–5 are decided by `workflow.nextRecommendedPhase.status` alone — one status, one branch, no other field consulted:
|
|
99
|
+
|
|
100
|
+
3. **Start next phase** — `status` is `ready`. Propose `nextRecommendedPhase.phase` as the next run's `--task-type` and quote its `rationale` as the reason. This is the only status under which a named phase may be launched, so it is the only branch that proposes a run. A `ready` pointer to `release-handoff` already implies an `accepted` final-verification verdict (the report validator refuses that routing target otherwise), so do not re-gate it here.
|
|
101
|
+
4. **Need more information** — `status` is `pending` (the last run did not settle where this task goes next) or `blocked` (it did settle, and the answer is that something outside the run has to change first). Neither proposes a run, and a leftover `phase` name does not change that — `prepare` keeps the name when it lowers a pointer to `pending`, so read `status`, not the emptiness of `phase`. Show the `rationale`, and for `blocked` state what it names as the obstacle. The way forward is a fresh brief, a clarification response, or a re-run of the current phase — not a next-phase launch.
|
|
102
|
+
5. **Task complete (terminal)** — `status` is `terminal`: the task lifecycle ends here. This is **not** a "next phase" — do not propose a new okstra run. Surface the latest report and ask the user whether any follow-up task should be opened separately.
|
|
101
103
|
|
|
102
104
|
### status.4 — Update workStatus (write)
|
|
103
105
|
|
|
@@ -135,7 +137,7 @@ Confirm in Korean (the block below shows the message shape only — render the a
|
|
|
135
137
|
|
|
136
138
|
| Manifest state | Inferred display |
|
|
137
139
|
|---|---|
|
|
138
|
-
| `currentStatus == "completed"` AND `workflow.nextRecommendedPhase == "
|
|
140
|
+
| `currentStatus == "completed"` AND `workflow.nextRecommendedPhase.status == "terminal"` | `done` (inferred) |
|
|
139
141
|
| `currentStatus == "completed"` AND `workflow.currentPhaseState == "completed"` | `phase-done` (inferred) |
|
|
140
142
|
| `currentStatus == "contract-violated"` OR `workflow.currentPhaseState == "blocked"` | `blocked` (inferred) |
|
|
141
143
|
| anything else | `in-progress` (default) |
|
|
@@ -42,6 +42,7 @@ Omit `--task-group` for the whole project. The JSON shape (all durations are **r
|
|
|
42
42
|
|
|
43
43
|
- `taskGroup` — the scope (`null` = whole project), `taskCount` — number of tasks.
|
|
44
44
|
- `tasks[]` — per task: `taskKey, taskGroup, taskId, taskType, workCategory, workStatus, currentPhase, currentPhaseState, nextRecommendedPhase, latestRunStatus, updatedAt, reportPath, runCount, cpuSumMs, wallClockMs, errorCount`, plus `criticRuns, criticGapsProposed, criticGapsMerged, criticGapsRejected, criticGapsUnverified`.
|
|
45
|
+
- `nextRecommendedPhase` is an **object** `{phase, status, rationale}`, not a phase-name string. Anywhere you surface it, print `nextRecommendedPhase.phase` — or `--` when that string is empty, which is what `status` `pending` / `blocked` / `terminal` all produce. Never interpolate the object into a table cell or a sentence: it renders as a raw dict.
|
|
45
46
|
- `totals` — `runs, cpuSumMs, wallClockMs, errors`, plus `byWorkStatus`, `byWorkCategory`, `byCurrentPhase`, `byTaskType` (each a `{value: count}` map), plus `critic` (`runsWithCritic, gapsProposed, gapsMerged, gapsRejected, gapsUnverified`).
|
|
46
47
|
- The coverage critic is opt-in, so `totals.critic.runsWithCritic == 0` means it was never used — report that as "not used", never as a zero-yield result. When it IS non-zero, the merge rate (`gapsMerged / gapsProposed`) is what tells the user whether the pass earns its tokens; state it only when at least one run used it.
|
|
47
48
|
|
|
@@ -32,7 +32,7 @@ Every wizard call returns JSON. The two shapes you'll see:
|
|
|
32
32
|
"progress": { "index": 5, "total": 11, "remaining": 6 } } }
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
-
Every non-terminal `next` carries a `progress` object (`done` / `aborted` omit it). It holds `index` (1-based number of the **current screen**; pick_group counts as one screen), `total` (the wizard's forward estimate of the full screen count — may grow by a few when the user opens a branch, e.g.
|
|
35
|
+
Every non-terminal `next` carries a `progress` object (`done` / `aborted` omit it). It holds `index` (1-based number of the **current screen**; pick_group counts as one screen), `total` (the wizard's forward estimate of the full screen count — may grow by a few when the user opens a branch, e.g. role-add or extra role-model slots), `remaining` (`total − index`), and **`label`** — the ready-to-render marker the wizard already composed (e.g. `Step 10/11 · 1 step remaining`, or `Step 11/11 · final step` on the final screen). **Always suffix the rendered prompt with `progress.label` verbatim** — see Step 3. Do not recompute the marker or decide "final step" yourself; only the wizard knows whether more screens follow.
|
|
36
36
|
|
|
37
37
|
```json
|
|
38
38
|
{ "ok": false, "error": "approved plan has no APPROVED marker: ...",
|
|
@@ -179,14 +179,14 @@ Repeat until `next.kind == "done"` (or `"aborted"` — terminal cancel, see "How
|
|
|
179
179
|
That is the entire interactive flow. The wizard handles:
|
|
180
180
|
|
|
181
181
|
- new-vs-existing task split (remaining work — `workStatus != done` — top-3 newest recommendations + Enter directly), task-group / task-id slug validation (task-group offers the top-3 newest candidates combining recent task use + recent `.okstra/briefs/<group>/` creation activity + Enter directly; task-id offers the top-3 recent candidates from the same group + Enter directly),
|
|
182
|
-
- task-type pick (3
|
|
182
|
+
- task-type pick (3 options + Enter directly; Enter directly is validated against the full task-type whitelist in a follow-up `text` step). What fills those three depends on `workflow.nextRecommendedPhase` — an object `{phase, status, rationale}`, not a phase-name string. `status: ready` gives the classic trio: its `phase` marked recommended, re-run the current phase, the lifecycle's next step. Any other status contributes nothing at all, since both the recommended slot and the next-step slot derive from that phase — re-run the current phase becomes the first option and the remaining slots fill unlabelled from recently used task-types and then the whitelist. Never recover a phase name from a non-`ready` pointer and propose it yourself: prepare deliberately lowers the pointer to `pending` while its run is unfinished, and the missing recommendation is that signal,
|
|
183
183
|
- brief path — **asked only for entry task-types (requirements-discovery / error-analysis / improvement-discovery / project-analysis / feature-analysis / change-impact-analysis)** (same-group `.okstra/briefs/<task-group>/**/*.md` candidates first, sorted by the newer of file-created/modified time and latest task-catalog use; direct input last; `Keep / Change` for existing entry tasks). `project-analysis`, `feature-analysis`, and `change-impact-analysis` are brief entry task types. A downstream lifecycle task-type auto carries in the manifest's brief, and when no registered brief exists a `brief_carry` 3-option prompt appears (recommend switching to entry / Enter directly / Abort). `release-handoff` has no brief step at all — prepare generates the input document that cites the verification report,
|
|
184
184
|
- analysis-input sub-flow — `project-analysis` has no target or evidence step. `feature-analysis` runs `project_evidence_pick` / `project_evidence`, then `analysis_target_pick` / `analysis_target`; the target is required even when the user skips project evidence. `change-impact-analysis` runs `feature_evidence_pick` / `feature_evidence`, then `project_evidence_pick` / `project_evidence`. The evidence steps show accepted compatible reports first and retain direct-path entry; do not invent a different relation or reorder these steps,
|
|
185
185
|
- base-ref pick + git rev-parse validation (skipped when reusing an active worktree),
|
|
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)
|
|
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
|
-
- `
|
|
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
|
|
188
|
+
- launch selection after identity/worktree steps: leader session read-only (current-session) → 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). 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
|
+
- **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
190
|
- **re-verification scope (`reverify_scope_pick`, `implementation-planning` clarification re-runs only)** — asked right before `confirm`, and **only when the re-run is narrowable** (every answered `C-NNN` traces back to a stage in the prior report). 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). 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. When the re-run is not narrowable the step does not appear — full is already fixed, and the confirmation block's `reverify-scope` line says which answered id broke the link,
|
|
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.
|
|
@@ -29,24 +29,9 @@ When `**Evidence ledger:** required-v1` is present, append one canonical row to
|
|
|
29
29
|
|
|
30
30
|
Every file citation in the result MUST use backticks, a line suffix, and the same project-relative path its ledger row carries — for example `src/config/env.ts:1-22`. A bare filename (`env.ts:1-22`) does not match its row and fails exactly like a file you never opened, however many times you cited the full path earlier. A cited path without a matching ledger row fails Phase 7 in `validators/validate-run.py` `validate_worker_results_audit()`. Do not add a row for a file you did not open.
|
|
31
31
|
|
|
32
|
-
##
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
1. `**Project Root:** <absolute-path>`
|
|
37
|
-
2. `**Prompt History Path:** <project-relative-path>`
|
|
38
|
-
3. `**Result Path:** <project-relative-path>`
|
|
39
|
-
4. `Assigned worker prompt history path: <absolute-path>`
|
|
40
|
-
5. `**Worker Preamble Path:** <absolute-path>`
|
|
41
|
-
6. `**Worker Error Contract Path:** <absolute-path>`
|
|
42
|
-
7. `**Coding preflight pack:** <absolute-path>`
|
|
43
|
-
8. `**Evidence ledger:** required-v1`
|
|
44
|
-
9. `**Evidence citations:** <rule>` — the ledger-row and citation-path rule of §"Evidence read ledger", restated inline so it is in front of you before your first citation.
|
|
45
|
-
10. `**Errors log path:** <absolute-path>`
|
|
46
|
-
11. `**Errors sidecar path:** <absolute-path>`
|
|
47
|
-
12. `**Read scope:** <allowlist>`
|
|
48
|
-
|
|
49
|
-
The implementation body additionally carries `**Worktree:**` and its role-sidecar inputs. Do not synthesize any missing path.
|
|
32
|
+
## Injected paths
|
|
33
|
+
|
|
34
|
+
Use the path headers in the dispatch prompt exactly as written, including `**Coding preflight pack:**` and `**Worktree:**` when present. Do not synthesize any missing path.
|
|
50
35
|
|
|
51
36
|
## Return message to the lead
|
|
52
37
|
|
|
@@ -36,7 +36,6 @@ taskType: "{{FM_TASK_TYPE}}"
|
|
|
36
36
|
- Last Completed Phase: `{{WORKFLOW_LAST_COMPLETED_PHASE}}`
|
|
37
37
|
- Next Recommended Phase: `{{WORKFLOW_NEXT_RECOMMENDED_PHASE}}`
|
|
38
38
|
- Awaiting Approval: `{{WORKFLOW_AWAITING_APPROVAL}}`
|
|
39
|
-
- Routing Status: `{{WORKFLOW_ROUTING_STATUS}}`
|
|
40
39
|
- Fix cycles: {{FIX_CYCLES_SUMMARY}}
|
|
41
40
|
|
|
42
41
|
## Phase States
|
|
@@ -63,6 +63,7 @@
|
|
|
63
63
|
{% for row in items %}
|
|
64
64
|
{% set is_closed = row.status in ['resolved', 'obsolete'] %}
|
|
65
65
|
{% set approval_context = row.approvalContext | default(None) %}
|
|
66
|
+
{% set options = row.options | default([]) %}
|
|
66
67
|
<article class="clarification-item" id="id-{{ row.id }}" data-response-id="{{ row.id }}" data-kind="{{ row.kind }}" data-status="{{ row.status }}">
|
|
67
68
|
<p class="eyebrow">{{ row.id }} · {{ row.kind }}</p>
|
|
68
69
|
{{ row.statement | paragraphs }}
|
|
@@ -76,9 +77,9 @@
|
|
|
76
77
|
</p>
|
|
77
78
|
</div>
|
|
78
79
|
{% endif %}
|
|
79
|
-
{% if
|
|
80
|
+
{% if options %}
|
|
80
81
|
<ol class="clarification-options">
|
|
81
|
-
{% for option in
|
|
82
|
+
{% for option in options %}
|
|
82
83
|
<li class="clarification-option{% if option.role == 'recommended' %} is-recommended{% endif %}">
|
|
83
84
|
<p class="clarification-option-answer">{{ option.answer }}{% if option.role == 'recommended' %} <span class="badge">{{ t('macros.forms.recommended') }}</span>{% endif %}</p>
|
|
84
85
|
{% if option.disposition | default(None) %}<p class="clarification-option-disposition"><code>{{ option.disposition }}</code></p>{% endif %}
|
|
@@ -93,10 +94,10 @@
|
|
|
93
94
|
</ol>
|
|
94
95
|
{% endif %}
|
|
95
96
|
<label for="response-{{ row.id }}">{{ t('macros.forms.your-answer-to-id') | replace('{id}', row.id) }}</label>
|
|
96
|
-
{% if row.kind == 'decision' and approval_context and
|
|
97
|
+
{% if row.kind == 'decision' and approval_context and options %}
|
|
97
98
|
<select id="response-{{ row.id }}" data-response-id="{{ row.id }}"{% if is_closed %} disabled{% endif %}>
|
|
98
99
|
<option value="">{{ t('macros.forms.choose-one') }}</option>
|
|
99
|
-
{% for option in
|
|
100
|
+
{% for option in options %}<option value="{{ option.answer }}" data-disposition="{{ option.disposition | default('') }}"{% if option.role == 'recommended' %} data-recommended="true"{% endif %}{% if row.userInput | default('') == option.answer %} selected{% endif %}>{{ option.answer }}</option>{% endfor %}
|
|
100
101
|
</select>
|
|
101
102
|
{% else %}
|
|
102
103
|
<textarea id="response-{{ row.id }}" data-response-id="{{ row.id }}" rows="4"{% if is_closed %} disabled{% endif %}>{{ row.userInput | default('') }}</textarea>
|
|
@@ -41,6 +41,6 @@
|
|
|
41
41
|
<section data-report-section="release-route">
|
|
42
42
|
<h2>{{ t('tasks.final-verification.release-path') }}</h2>
|
|
43
43
|
{{ render_narrative(narrative.releaseReadiness, "finalVerification.userNarrative.releaseReadiness") }}
|
|
44
|
-
{% if releaseAllowed %}<p>{{ final.routingRecommendation | inline_code }}</p>{% else %}<p>{{ t('tasks.final-verification.the-current-verdict-does-not-allow-release-h') }}</p>{% endif %}
|
|
44
|
+
{% if releaseAllowed %}<p><strong><code>{{ final.routingRecommendation.target }}</code></strong> — {{ final.routingRecommendation.rationale | inline_code }}</p>{% else %}<p>{{ t('tasks.final-verification.the-current-verdict-does-not-allow-release-h') }}</p>{% endif %}
|
|
45
45
|
</section>
|
|
46
46
|
{% endblock %}
|
|
@@ -53,6 +53,6 @@
|
|
|
53
53
|
|
|
54
54
|
<section data-report-section="next-step" data-report-field="implementation.routingRecommendation">
|
|
55
55
|
<h2>{{ t('tasks.implementation.next-step') }}</h2>
|
|
56
|
-
<p>{{ implementation.routingRecommendation | inline_code }}</p>
|
|
56
|
+
<p><strong><code>{{ implementation.routingRecommendation.target }}</code></strong> — {{ implementation.routingRecommendation.rationale | inline_code }}</p>
|
|
57
57
|
</section>
|
|
58
58
|
{% endblock %}
|
|
@@ -25,32 +25,11 @@ Every file citation in the result MUST use backticks, a line suffix, and the sam
|
|
|
25
25
|
|
|
26
26
|
## Evidence command ledger
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
Follow the `**Evidence commands:**` header in the dispatch prompt. That header is the worker-facing rule.
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
## Injected paths
|
|
31
31
|
|
|
32
|
-
Do not
|
|
33
|
-
|
|
34
|
-
Write the command as you ran it. Nothing scans these rows for secrets, so a row is never refused for how a setting is named — `AUTH_MODE=off` and `TOKEN_TTL=60` record verbatim. What the row needs to carry is the command's shape, its keys; a value that must not be written down is yours to leave out or pass as a `$VAR` reference, and at execution time that value comes from the environment or from the person running the command.
|
|
35
|
-
|
|
36
|
-
## Anchor headers (lead-injected, BLOCKING)
|
|
37
|
-
|
|
38
|
-
Every initial analysis prompt begins with these generated anchors in this exact order, before any other content:
|
|
39
|
-
|
|
40
|
-
1. `**Project Root:** <absolute-path>`
|
|
41
|
-
2. `**Prompt History Path:** <project-relative-path>`
|
|
42
|
-
3. `**Result Path:** <project-relative-path>`
|
|
43
|
-
4. `**Audit sidecar path:** <absolute-path>` — the generated audit destination; write the sidecar here and never synthesize a `runs/<task-type>/...` path.
|
|
44
|
-
5. `Assigned worker prompt history path: <absolute-path>`
|
|
45
|
-
6. `**Worker Preamble Path:** <absolute-path>` — selects this analysis preamble.
|
|
46
|
-
7. `**Worker Error Contract Path:** <absolute-path>` — shared by every initial audience.
|
|
47
|
-
8. `**Evidence ledger:** required-v1`
|
|
48
|
-
9. `**Evidence citations:** <rule>` — the ledger-row and citation-path rule of §"Evidence read ledger", restated inline so it is in front of you before your first citation.
|
|
49
|
-
10. `**Errors log path:** <absolute-path>`
|
|
50
|
-
11. `**Errors sidecar path:** <absolute-path>`
|
|
51
|
-
12. `**Read scope:** <allowlist>`
|
|
52
|
-
|
|
53
|
-
`final-verification` additionally carries its six verification-target anchors. `improvement-discovery` carries `**Phase 1.5 Grilling Log:**`. Reverify prompts are lightweight and do not use this preamble.
|
|
32
|
+
Use the path headers in the dispatch prompt exactly as written. Do not synthesize a missing path from a run-directory pattern. `final-verification` may carry verification-target headers. `improvement-discovery` may carry `**Phase 1.5 Grilling Log:**`. Treat extra generated headers the same way. Reverify prompts are lightweight and do not use this preamble.
|
|
54
33
|
|
|
55
34
|
## Worker output sections
|
|
56
35
|
|
|
@@ -65,18 +44,6 @@ Every analysis result starts with YAML frontmatter containing the task identity,
|
|
|
65
44
|
|
|
66
45
|
Every item has a worker-local ID and file:line evidence where code evidence exists. Sections 1–5 are the common core: feasibility, requirement interpretation, hidden assumptions, alternatives, and execution risk. Section 6 is the only legal home for specialization and is not consensus input.
|
|
67
46
|
|
|
68
|
-
### Selected-direction planning ownership
|
|
69
|
-
|
|
70
|
-
For `implementation-planning` with `selected-direction.json`, sections 1–5 read that snapshot and the original requirements, then assess only its realization into files, interfaces, stages, validation, rollback, bidirectional requirement links, and `direction-invalidated` evidence. Direction selection remains upstream of this prompt.
|
|
71
|
-
|
|
72
|
-
### Legacy candidate-comparison ownership
|
|
73
|
-
|
|
74
|
-
For an implementation-planning compatibility rerun without the snapshot, sections 1–5 retain candidate comparison, trade-off, recommendation, and `P-Opt-*` evidence.
|
|
75
|
-
|
|
76
|
-
### Ticket Tagging
|
|
77
|
-
|
|
78
|
-
For `requirements-discovery`, `error-analysis`, and `implementation-planning`, tag every section 1–5 item with its related ticket. Use `Issue / Ticket`, fall back to Task ID, then `unknown`; comma-separate multiple tickets.
|
|
79
|
-
|
|
80
47
|
## Return message to the lead
|
|
81
48
|
|
|
82
49
|
Begin the inline return with the model identity copied verbatim from the `**Model:** <Role>, <modelExecutionValue>` line in the prompt, followed by the status summary. Never invent or abbreviate the model.
|