okstra 0.178.0 → 0.179.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/commands/execute/plan-verify.mjs +1 -1
- package/dist/commands/execute/worktree-status.mjs +8 -2
- package/dist/commands/execute/worktree-status.mjs.map +1 -1
- package/dist/commands/lifecycle/install.mjs +1 -1
- package/dist/commands/lifecycle/install.mjs.map +1 -1
- package/dist/commands/report/render-final-report.mjs +3 -3
- package/docs/architecture/storage-model.md +3 -3
- package/docs/architecture.md +10 -9
- package/docs/cli.md +11 -13
- package/docs/for-ai/skills/okstra-inspect.md +3 -3
- package/docs/for-ai/skills/okstra-schedule-gen.md +2 -2
- package/docs/for-ai/skills/okstra-user-response.md +2 -2
- package/docs/project-structure-overview.md +10 -11
- package/docs/task-process/implementation-planning.md +1 -1
- package/docs/task-process/implementation.md +1 -1
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/workers/report-writer-worker.md +11 -12
- package/runtime/bin/lib/okstra/globals.sh +2 -2
- package/runtime/bin/lib/okstra/interactive.sh +1 -1
- package/runtime/bin/lib/okstra/usage.sh +11 -9
- package/runtime/bin/lib/okstra-ctl/cmd-rerun.sh +1 -1
- package/runtime/bin/okstra-central.sh +2 -2
- package/runtime/bin/okstra-render-final-report.py +1 -1
- package/runtime/bin/okstra-token-usage.py +1 -1
- package/runtime/prompts/launch.template.md +1 -1
- package/runtime/prompts/lead/adapters/cmux.md +6 -1
- package/runtime/prompts/lead/context-loader.md +3 -2
- package/runtime/prompts/lead/convergence.md +3 -3
- package/runtime/prompts/lead/okstra-lead-contract.md +5 -5
- package/runtime/prompts/lead/plan-body-verification.md +3 -3
- package/runtime/prompts/lead/report-writer.md +21 -20
- package/runtime/prompts/lead/team-contract.md +1 -1
- package/runtime/prompts/profiles/_common-contract.md +5 -4
- package/runtime/prompts/profiles/_implementation-deliverable.md +1 -0
- package/runtime/prompts/profiles/_implementation-executor.md +2 -1
- package/runtime/prompts/profiles/_implementation-verifier.md +1 -1
- package/runtime/prompts/profiles/implementation-planning.md +9 -5
- package/runtime/prompts/profiles/implementation.md +4 -4
- package/runtime/prompts/profiles/improvement-discovery.md +2 -2
- package/runtime/prompts/wizard/prompts.ko.json +1 -0
- package/runtime/python/okstra_ctl/adapters/providers/antigravity/adapter.py +5 -4
- package/runtime/python/okstra_ctl/adapters/providers/claude/adapter.py +5 -4
- package/runtime/python/okstra_ctl/adapters/providers/codex/adapter.py +2 -2
- package/runtime/python/okstra_ctl/adapters/providers/grok/adapter.py +71 -9
- package/runtime/python/okstra_ctl/adapters/providers/kimi/adapter.py +5 -4
- package/runtime/python/okstra_ctl/agent_prompt_cli.py +77 -6
- package/runtime/python/okstra_ctl/analysis_inputs.py +5 -3
- package/runtime/python/okstra_ctl/analysis_packet.py +21 -0
- package/runtime/python/okstra_ctl/backfill.py +12 -5
- package/runtime/python/okstra_ctl/consumers.py +70 -3
- package/runtime/python/okstra_ctl/convergence_engine.py +43 -17
- package/runtime/python/okstra_ctl/convergence_store.py +13 -2
- package/runtime/python/okstra_ctl/dispatch_core.py +85 -14
- package/runtime/python/okstra_ctl/dispatch_state.py +34 -20
- package/runtime/python/okstra_ctl/domain/worker_exec.py +13 -34
- package/runtime/python/okstra_ctl/domain/worker_presentation.py +128 -0
- package/runtime/python/okstra_ctl/execution_manifest.py +22 -3
- package/runtime/python/okstra_ctl/execution_mutation_audit.py +35 -1
- package/runtime/python/okstra_ctl/final_report_paths.py +77 -1
- package/runtime/python/okstra_ctl/handoff.py +1 -2
- package/runtime/python/okstra_ctl/implementation_outcome.py +1 -1
- package/runtime/python/okstra_ctl/index.py +4 -4
- package/runtime/python/okstra_ctl/initial_prompt_materialization.py +26 -12
- package/runtime/python/okstra_ctl/listing.py +4 -2
- package/runtime/python/okstra_ctl/manager_launch.py +1 -1
- package/runtime/python/okstra_ctl/manager_sync.py +1 -1
- package/runtime/python/okstra_ctl/path_hints.py +2 -2
- package/runtime/python/okstra_ctl/paths.py +24 -15
- package/runtime/python/okstra_ctl/plan_run_root.py +9 -5
- package/runtime/python/okstra_ctl/recap.py +3 -2
- package/runtime/python/okstra_ctl/reconcile.py +3 -1
- package/runtime/python/okstra_ctl/render.py +22 -22
- package/runtime/python/okstra_ctl/report_finalize.py +4 -4
- package/runtime/python/okstra_ctl/rollup.py +1 -1
- package/runtime/python/okstra_ctl/run.py +170 -294
- package/runtime/python/okstra_ctl/run_audit.py +5 -5
- package/runtime/python/okstra_ctl/run_index_row.py +2 -2
- package/runtime/python/okstra_ctl/session_transcript.py +89 -0
- package/runtime/python/okstra_ctl/stage_ledger.py +72 -0
- package/runtime/python/okstra_ctl/stage_map.py +28 -29
- package/runtime/python/okstra_ctl/stage_targets.py +61 -0
- package/runtime/python/okstra_ctl/user_response.py +97 -12
- package/runtime/python/okstra_ctl/wizard.py +102 -78
- package/runtime/python/okstra_ctl/worker_prompt_body.py +6 -7
- package/runtime/python/okstra_ctl/worker_runner.py +76 -213
- package/runtime/python/okstra_ctl/workflow.py +1 -1
- package/runtime/python/okstra_ctl/wrapper_status.py +23 -0
- package/runtime/python/okstra_ctl/write_policy.py +51 -9
- package/runtime/python/okstra_project/state.py +2 -2
- package/runtime/python/okstra_token_usage/__init__.py +1 -1
- package/runtime/python/okstra_token_usage/cli.py +3 -3
- package/runtime/python/okstra_token_usage/report.py +7 -24
- package/runtime/schemas/convergence-groups-v1.0.schema.json +1 -1
- package/runtime/schemas/convergence-groups-v2.0.schema.json +1 -1
- package/runtime/schemas/final-report-v2.0.schema.json +11 -1
- package/runtime/skills/okstra-inspect/facets/history.md +3 -3
- package/runtime/skills/okstra-inspect/facets/recap.md +1 -1
- package/runtime/skills/okstra-inspect/facets/report.md +5 -5
- package/runtime/skills/okstra-inspect/facets/status.md +2 -2
- package/runtime/skills/okstra-pr-gen/SKILL.md +1 -1
- package/runtime/skills/okstra-run/SKILL.md +1 -1
- package/runtime/skills/okstra-schedule-gen/SKILL.md +1 -1
- package/runtime/skills/okstra-user-response/SKILL.md +3 -3
- package/runtime/templates/project-docs/task-index.template.md +1 -1
- package/runtime/templates/report-writer-prompt-preamble.md +1 -1
- package/runtime/validators/forbidden_actions.py +76 -5
- package/runtime/validators/lib/fixtures.sh +14 -10
- package/runtime/validators/lib/runners.sh +1 -1
- package/runtime/validators/validate-implementation-plan-stages.py +3 -0
- package/runtime/validators/validate-report-views.py +1 -1
- package/runtime/validators/validate-run.py +95 -37
- package/runtime/validators/validate_session_conformance.py +44 -8
|
@@ -14,9 +14,46 @@ def _stem(data_path: Path) -> str:
|
|
|
14
14
|
return name[: -len(DATA_JSON_SUFFIX)] if name.endswith(DATA_JSON_SUFFIX) else data_path.stem
|
|
15
15
|
|
|
16
16
|
|
|
17
|
+
def require_approved_plan_record(path: Path) -> Path:
|
|
18
|
+
"""`--approved-plan` accepts the report record only.
|
|
19
|
+
|
|
20
|
+
A full reading copy (`.md`) is rejected with the sibling record path.
|
|
21
|
+
Schema-v1 plans have no record and cannot enter this gate.
|
|
22
|
+
"""
|
|
23
|
+
resolved = Path(path)
|
|
24
|
+
if is_full_reading_copy_path(resolved):
|
|
25
|
+
suggested = final_report_data_path(resolved)
|
|
26
|
+
raise ValueError(
|
|
27
|
+
f"--approved-plan must be the report record (.data.json), not the "
|
|
28
|
+
f"full reading copy: {path}\n"
|
|
29
|
+
" schema-v1 plans have no record and cannot be used here.\n"
|
|
30
|
+
f" for a schema-v2 plan use: {suggested}"
|
|
31
|
+
)
|
|
32
|
+
if not is_report_record_path(resolved):
|
|
33
|
+
raise ValueError(
|
|
34
|
+
f"--approved-plan must be a report record ending in .data.json: {path}"
|
|
35
|
+
)
|
|
36
|
+
if not resolved.is_file():
|
|
37
|
+
raise ValueError(f"approved plan record not found: {path}")
|
|
38
|
+
return resolved
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def is_report_record_path(path: Path) -> bool:
|
|
42
|
+
return path.name.endswith(DATA_JSON_SUFFIX)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def is_full_reading_copy_path(path: Path) -> bool:
|
|
46
|
+
return path.name.endswith(MARKDOWN_SUFFIX)
|
|
47
|
+
|
|
48
|
+
|
|
17
49
|
def final_report_data_path(report_path: Path) -> Path:
|
|
18
|
-
"""Return the
|
|
50
|
+
"""Return the report record path for a final-report pair.
|
|
51
|
+
|
|
52
|
+
Already a `.data.json` path is returned unchanged.
|
|
53
|
+
"""
|
|
19
54
|
name = report_path.name
|
|
55
|
+
if name.endswith(DATA_JSON_SUFFIX):
|
|
56
|
+
return report_path
|
|
20
57
|
if name.endswith(MARKDOWN_SUFFIX):
|
|
21
58
|
return report_path.with_name(name[: -len(MARKDOWN_SUFFIX)] + DATA_JSON_SUFFIX)
|
|
22
59
|
return report_path.with_suffix(DATA_JSON_SUFFIX)
|
|
@@ -30,6 +67,45 @@ def final_report_markdown_path(data_path: Path) -> Path:
|
|
|
30
67
|
return data_path.with_suffix(MARKDOWN_SUFFIX)
|
|
31
68
|
|
|
32
69
|
|
|
70
|
+
def report_record_rel_from_legacy_pointer(relative: str) -> str:
|
|
71
|
+
"""Turn a stored pointer into a report-record relative path.
|
|
72
|
+
|
|
73
|
+
New rows store the record. Rows written before that stored the full
|
|
74
|
+
reading copy (`.md`); the record is the sibling `.data.json`.
|
|
75
|
+
"""
|
|
76
|
+
relative = str(relative or "")
|
|
77
|
+
if not relative:
|
|
78
|
+
return ""
|
|
79
|
+
path = Path(relative)
|
|
80
|
+
if is_report_record_path(path):
|
|
81
|
+
return relative
|
|
82
|
+
return str(final_report_data_path(path))
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def index_report_record_rel(row: dict) -> str:
|
|
86
|
+
"""Report-record pointer from a run-index row.
|
|
87
|
+
|
|
88
|
+
New rows use `finalReportRecordRel`. Older rows used `finalReportRel`
|
|
89
|
+
for the full reading copy.
|
|
90
|
+
"""
|
|
91
|
+
current = str(row.get("finalReportRecordRel") or "")
|
|
92
|
+
if current:
|
|
93
|
+
return current
|
|
94
|
+
return report_record_rel_from_legacy_pointer(str(row.get("finalReportRel") or ""))
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def timeline_report_record_rel(run: dict) -> str:
|
|
98
|
+
"""Report-record pointer from a timeline / recap run entry.
|
|
99
|
+
|
|
100
|
+
New entries use `reportRecordPath`. Older entries used `reportPath`
|
|
101
|
+
for the full reading copy.
|
|
102
|
+
"""
|
|
103
|
+
current = str(run.get("reportRecordPath") or "")
|
|
104
|
+
if current:
|
|
105
|
+
return current
|
|
106
|
+
return report_record_rel_from_legacy_pointer(str(run.get("reportPath") or ""))
|
|
107
|
+
|
|
108
|
+
|
|
33
109
|
def translation_source_path(data_path: Path) -> Path:
|
|
34
110
|
"""Return the translator's work list for a final-report data.json path."""
|
|
35
111
|
return data_path.with_name(_stem(data_path) + TRANSLATION_SOURCE_SUFFIX)
|
|
@@ -81,8 +81,7 @@ def latest_whole_task_fv_release_ready(project_root, project_id: str,
|
|
|
81
81
|
if ((data.get("header") or {}).get("taskType") == "final-verification"
|
|
82
82
|
and data.get("verificationScope") == "whole-task"
|
|
83
83
|
and release_handoff_allowed(data)):
|
|
84
|
-
|
|
85
|
-
return str(md if md.is_file() else dj)
|
|
84
|
+
return str(dj)
|
|
86
85
|
return ""
|
|
87
86
|
|
|
88
87
|
|
|
@@ -212,7 +212,7 @@ def _update_task_catalog(project_root: Path, manifest: dict[str, Any]) -> bool:
|
|
|
212
212
|
entry["nextRecommendedPhase"] = next_phase.promote(
|
|
213
213
|
workflow.get("nextRecommendedPhase")
|
|
214
214
|
)
|
|
215
|
-
entry["
|
|
215
|
+
entry["latestReportRecordPath"] = manifest.get("latestReportRecordPath", "")
|
|
216
216
|
entry["latestRunStatus"] = manifest.get("latestRunStatus", entry.get("latestRunStatus", ""))
|
|
217
217
|
entry["currentStatus"] = manifest.get("currentStatus", entry.get("currentStatus", ""))
|
|
218
218
|
entry["phaseOutcome"] = outcome
|
|
@@ -33,7 +33,7 @@ def _replace_or_append_row(path: Path, run_id: str, row: dict) -> None:
|
|
|
33
33
|
def record_start(home: Path, *, project_id: str, project_root: str,
|
|
34
34
|
task_group: str, task_id: str, task_type: str,
|
|
35
35
|
run_seq: int, when: str, workers: list, lead_model: str,
|
|
36
|
-
run_dir_rel: str,
|
|
36
|
+
run_dir_rel: str, final_report_record_rel: str,
|
|
37
37
|
argv: list, cwd: str,
|
|
38
38
|
env_overrides: dict,
|
|
39
39
|
okstra_version: str = "",
|
|
@@ -55,7 +55,7 @@ def record_start(home: Path, *, project_id: str, project_root: str,
|
|
|
55
55
|
run_seq=run_seq, status=initial_status, started_at=when,
|
|
56
56
|
finished_at=when if is_terminal else None,
|
|
57
57
|
workers=workers, lead_model=lead_model, validation="not-run",
|
|
58
|
-
run_dir_rel=run_dir_rel,
|
|
58
|
+
run_dir_rel=run_dir_rel, final_report_record_rel=final_report_record_rel,
|
|
59
59
|
final_status_rel=final_status_rel,
|
|
60
60
|
execution_manifest_path=execution_manifest_path,
|
|
61
61
|
role_execution_refs=role_execution_refs)
|
|
@@ -115,7 +115,7 @@ def reserve_run_in_active(home: Path, *,
|
|
|
115
115
|
project_id: str, project_root: str,
|
|
116
116
|
task_group: str, task_id: str, task_type: str,
|
|
117
117
|
run_seq: int, when: str,
|
|
118
|
-
run_dir_rel: str,
|
|
118
|
+
run_dir_rel: str, final_report_record_rel: str,
|
|
119
119
|
final_status_rel: str = "") -> None:
|
|
120
120
|
"""예약 row 를 active.jsonl 에 즉시 기록한다. 중앙 락 보호 필수.
|
|
121
121
|
record_start 가 나중에 같은 (project, group, task, task_type, seq) 를 만나면
|
|
@@ -126,7 +126,7 @@ def reserve_run_in_active(home: Path, *,
|
|
|
126
126
|
task_group=task_group, task_id=task_id, task_type=task_type,
|
|
127
127
|
run_seq=run_seq, status="reserving", started_at=when,
|
|
128
128
|
finished_at=None, workers=[], lead_model="", validation="not-run",
|
|
129
|
-
run_dir_rel=run_dir_rel,
|
|
129
|
+
run_dir_rel=run_dir_rel, final_report_record_rel=final_report_record_rel,
|
|
130
130
|
final_status_rel=final_status_rel)
|
|
131
131
|
append_jsonl(home / "active.jsonl", row)
|
|
132
132
|
# project index 도 미리 한 줄 — record_start 가 나중에 update 하므로 동일 row 형태로.
|
|
@@ -98,6 +98,14 @@ _MATERIALIZABLE_AUDIENCES = frozenset({
|
|
|
98
98
|
"report-writer",
|
|
99
99
|
})
|
|
100
100
|
_CLARIFICATION_INPUT_PREFIX = "- Clarification response:"
|
|
101
|
+
# Both sides of a stage judge against the same answers. The verifier used to
|
|
102
|
+
# get only the path under `## Inputs`, so whether it read the override was left
|
|
103
|
+
# to the worker: one provider read it and judged on the replacement command,
|
|
104
|
+
# another did not and reported the superseded plan's command as a stage defect.
|
|
105
|
+
_CLARIFICATION_AUTHORITY_AUDIENCES = frozenset({
|
|
106
|
+
"implementation-executor",
|
|
107
|
+
"implementation-verifier",
|
|
108
|
+
})
|
|
101
109
|
_CLARIFICATION_AUTHORITY_HEADING = (
|
|
102
110
|
"# Clarification answers carried in (authoritative)"
|
|
103
111
|
)
|
|
@@ -578,8 +586,8 @@ def _resource_lines(
|
|
|
578
586
|
]
|
|
579
587
|
bodies = [resource.text.rstrip() for resource in item.resources]
|
|
580
588
|
clarification = (
|
|
581
|
-
|
|
582
|
-
if item.plan.audience
|
|
589
|
+
_eager_clarification_authority(item.clarification_input)
|
|
590
|
+
if item.plan.audience in _CLARIFICATION_AUTHORITY_AUDIENCES
|
|
583
591
|
else ""
|
|
584
592
|
)
|
|
585
593
|
if clarification:
|
|
@@ -630,7 +638,7 @@ def _resolve_clarification_input(
|
|
|
630
638
|
return relative, path
|
|
631
639
|
|
|
632
640
|
|
|
633
|
-
def
|
|
641
|
+
def _eager_clarification_authority(
|
|
634
642
|
clarification_input: tuple[str, Path] | None,
|
|
635
643
|
) -> str:
|
|
636
644
|
if clarification_input is None:
|
|
@@ -924,7 +932,7 @@ def _validate_existing_clarification_metadata(
|
|
|
924
932
|
item,
|
|
925
933
|
"Clarification response",
|
|
926
934
|
)
|
|
927
|
-
|
|
935
|
+
_validate_eager_clarification_authority(
|
|
928
936
|
context,
|
|
929
937
|
item,
|
|
930
938
|
text,
|
|
@@ -932,14 +940,14 @@ def _validate_existing_clarification_metadata(
|
|
|
932
940
|
)
|
|
933
941
|
|
|
934
942
|
|
|
935
|
-
def
|
|
943
|
+
def _validate_eager_clarification_authority(
|
|
936
944
|
context: _MaterializationContext,
|
|
937
945
|
item: _PromptItem,
|
|
938
946
|
text: str,
|
|
939
947
|
expected: str | None,
|
|
940
948
|
) -> None:
|
|
941
949
|
authority_expected = (
|
|
942
|
-
item.plan.audience
|
|
950
|
+
item.plan.audience in _CLARIFICATION_AUTHORITY_AUDIENCES
|
|
943
951
|
and context.request.delivery_mode is PromptDeliveryMode.EAGER_INCLUDE
|
|
944
952
|
and bool(_clarification_body(item.clarification_input))
|
|
945
953
|
)
|
|
@@ -951,7 +959,7 @@ def _validate_eager_executor_clarification_authority(
|
|
|
951
959
|
]
|
|
952
960
|
if not authority_expected and not heading_indexes:
|
|
953
961
|
return
|
|
954
|
-
if
|
|
962
|
+
if not authority_expected or expected is None:
|
|
955
963
|
_raise_existing_clarification_invalid(item, "clarification authority")
|
|
956
964
|
expected_lines = [
|
|
957
965
|
_CLARIFICATION_AUTHORITY_HEADING,
|
|
@@ -961,11 +969,17 @@ def _validate_eager_executor_clarification_authority(
|
|
|
961
969
|
f"{_CLARIFICATION_SOURCE_PREFIX} `{expected}`",
|
|
962
970
|
"",
|
|
963
971
|
]
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
972
|
+
# The block okstra injects is what this check owns; the carry-in body that
|
|
973
|
+
# follows it is the user's own document and may legitimately open with the
|
|
974
|
+
# same heading. Requiring the phrase to be unique made such a document
|
|
975
|
+
# undispatchable — the run failed `existing_prompt_invalid` with nothing in
|
|
976
|
+
# any contract telling the author to avoid that title.
|
|
977
|
+
matches = [
|
|
978
|
+
index
|
|
979
|
+
for index in heading_indexes
|
|
980
|
+
if lines[index:index + len(expected_lines)] == expected_lines
|
|
967
981
|
]
|
|
968
|
-
if not
|
|
982
|
+
if not matches or any(index < matches[0] for index in heading_indexes):
|
|
969
983
|
_raise_existing_clarification_invalid(item, "clarification authority")
|
|
970
984
|
|
|
971
985
|
|
|
@@ -1167,7 +1181,7 @@ def _result_paths(
|
|
|
1167
1181
|
worker_result = _required_string(state, "resultPath")
|
|
1168
1182
|
if worker_id != REPORT_WRITER_WORKER_ID:
|
|
1169
1183
|
return worker_result, None
|
|
1170
|
-
expected_report = _required_string(context.manifest, "
|
|
1184
|
+
expected_report = _required_string(context.manifest, "expectedReportRecordPath")
|
|
1171
1185
|
return str(final_report_data_path(Path(expected_report))), worker_result
|
|
1172
1186
|
|
|
1173
1187
|
|
|
@@ -169,11 +169,13 @@ def format_show(row: dict) -> str:
|
|
|
169
169
|
f"leadModel : {row.get('leadModel')}\n"
|
|
170
170
|
f"validation : {row.get('validation')}\n"
|
|
171
171
|
f"runDirRel : {row.get('runDirRel')}\n"
|
|
172
|
-
f"
|
|
172
|
+
f"finalReportRecordRel : {row.get('finalReportRecordRel') or row.get('finalReportRel')}\n"
|
|
173
173
|
f"invocationFile : {inv}\n"
|
|
174
174
|
)
|
|
175
175
|
|
|
176
176
|
|
|
177
177
|
def absolute_final_report_path(row: dict) -> Optional[Path]:
|
|
178
|
-
|
|
178
|
+
from .final_report_paths import index_report_record_rel
|
|
179
|
+
|
|
180
|
+
target = resolve_under_root(row.get("projectRoot", ""), index_report_record_rel(row))
|
|
179
181
|
return target.resolve() if target is not None else None
|
|
@@ -132,7 +132,7 @@ def _render_context(
|
|
|
132
132
|
)
|
|
133
133
|
for row in siblings:
|
|
134
134
|
lines.append(
|
|
135
|
-
f"- {row.get('taskKey')}: {row.get('latestRunStatus') or 'unknown'} {row.get('
|
|
135
|
+
f"- {row.get('taskKey')}: {row.get('latestRunStatus') or 'unknown'} {row.get('latestReportRecordPath') or ''}"
|
|
136
136
|
)
|
|
137
137
|
if not siblings:
|
|
138
138
|
lines.append("- none")
|
|
@@ -89,7 +89,7 @@ def _snapshot_child(project_root: Path, child: dict) -> dict:
|
|
|
89
89
|
workflow.get("nextRecommendedPhase")
|
|
90
90
|
),
|
|
91
91
|
"latestRunStatus": str(manifest.get("latestRunStatus") or manifest.get("currentStatus") or ""),
|
|
92
|
-
"
|
|
92
|
+
"latestReportRecordPath": str(manifest.get("latestReportRecordPath") or ""),
|
|
93
93
|
"finalVerdict": str(outcome.get("finalVerdict") or ""),
|
|
94
94
|
"crossProjectDependencies": _list_field(outcome, "crossProjectDependencies"),
|
|
95
95
|
"recommendedNextSteps": _list_field(outcome, "recommendedNextSteps"),
|
|
@@ -200,7 +200,7 @@ def _hydrate_active_run(
|
|
|
200
200
|
"runManifestPath": ctx.get("RUN_MANIFEST_RELATIVE_PATH", ""),
|
|
201
201
|
"teamStatePath": ctx.get("TEAM_STATE_RELATIVE_PATH", ""),
|
|
202
202
|
"promptSnapshotPath": ctx.get("RUN_PROMPT_SNAPSHOT_RELATIVE_PATH", ""),
|
|
203
|
-
"
|
|
203
|
+
"finalReportRecordPath": ctx.get("FINAL_REPORT_RECORD_RELATIVE_PATH", ""),
|
|
204
204
|
"convergenceStatePath": ctx.get("CONVERGENCE_STATE_RELATIVE_PATH", ""),
|
|
205
205
|
"finalStatusPath": ctx.get("FINAL_STATUS_RELATIVE_PATH", ""),
|
|
206
206
|
"validatorScriptPath": ctx.get("RUN_VALIDATOR_RELATIVE_PATH", ""),
|
|
@@ -651,7 +651,7 @@ def _relative_file_fields(project_root: Path, paths: Mapping[str, Any]) -> dict[
|
|
|
651
651
|
"CODEX_WORKER_PROMPT_RELATIVE_PATH": _rel(project_root, paths["codex_worker_prompt"]),
|
|
652
652
|
"ANTIGRAVITY_WORKER_PROMPT_RELATIVE_PATH": _rel(project_root, paths["antigravity_worker_prompt"]),
|
|
653
653
|
"REPORT_WRITER_WORKER_PROMPT_RELATIVE_PATH": _rel(project_root, paths["report_writer_worker_prompt"]),
|
|
654
|
-
"
|
|
654
|
+
"FINAL_REPORT_RECORD_RELATIVE_PATH": _rel(project_root, paths["final_report"]),
|
|
655
655
|
"CONVERGENCE_STATE_RELATIVE_PATH": _rel(
|
|
656
656
|
project_root, paths["convergence_state"]
|
|
657
657
|
),
|
|
@@ -83,11 +83,12 @@ def find_asset_root(
|
|
|
83
83
|
|
|
84
84
|
|
|
85
85
|
_STAGED_TASK_TYPES = ("implementation", "final-verification")
|
|
86
|
-
# 발견용:
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
86
|
+
# 발견용: 정본 `final-report-*.data.json` 과, 옛 원장 행이 가리키는 열람본.
|
|
87
|
+
_REPORT_NAME_RE = re.compile(r"^final-report-.+\.(?:md|data\.json)$")
|
|
88
|
+
# seq 추출용: `final-report-<task-type>-<NNN>.data.json` (옛 열람본 이름도).
|
|
89
|
+
_REPORT_SEQ_RE = re.compile(
|
|
90
|
+
r"^final-report-.+-(?P<seq>\d{3,})\.(?:md|data\.json)$"
|
|
91
|
+
)
|
|
91
92
|
_TASKS_SEGMENTS = Path(TASKS_RELATIVE).parts
|
|
92
93
|
|
|
93
94
|
|
|
@@ -95,10 +96,12 @@ def _newest_report(reports_dir: Path) -> Optional[Path]:
|
|
|
95
96
|
"""reports 디렉터리에서 mtime 최신 final-report. 동률이면 basename 큰 쪽."""
|
|
96
97
|
if not reports_dir.is_dir():
|
|
97
98
|
return None
|
|
98
|
-
|
|
99
|
+
files = [
|
|
99
100
|
entry for entry in reports_dir.iterdir()
|
|
100
101
|
if entry.is_file() and _REPORT_NAME_RE.match(entry.name)
|
|
101
102
|
]
|
|
103
|
+
records = [entry for entry in files if entry.name.endswith(".data.json")]
|
|
104
|
+
found = records or files
|
|
102
105
|
if not found:
|
|
103
106
|
return None
|
|
104
107
|
return max(found, key=lambda p: (p.stat().st_mtime, p.name))
|
|
@@ -182,7 +185,7 @@ class RunRef:
|
|
|
182
185
|
def report(self) -> Path:
|
|
183
186
|
if self.report_override is not None:
|
|
184
187
|
return self.report_override
|
|
185
|
-
return self.reports_dir / f"final-report{self._suffix}.
|
|
188
|
+
return self.reports_dir / f"final-report{self._suffix}.data.json"
|
|
186
189
|
|
|
187
190
|
@property
|
|
188
191
|
def manifest(self) -> Path:
|
|
@@ -374,16 +377,22 @@ def container_paths(project_root: Path, task_group: str, task_id: str) -> dict:
|
|
|
374
377
|
|
|
375
378
|
|
|
376
379
|
def next_run_seq(run_seq_dir: Path, task_type_segment: str) -> int:
|
|
377
|
-
"""run_seq_dir 안에서 `*-<task-type>-NNN.<ext
|
|
380
|
+
"""run_seq_dir 안에서 `*-<task-type>-NNN[.<ext>]` 항목을 스캔해 다음 seq 번호를
|
|
378
381
|
돌려준다. 디렉터리 부재 시 1.
|
|
382
|
+
|
|
383
|
+
파일과 디렉터리를 함께 센다. 이 카운터가 배정하는 자리 중 하나는
|
|
384
|
+
`prompts/duty-contracts-<task-type>-NNN/` 이라는 **디렉터리**다(확장자도 없다).
|
|
385
|
+
파일만 세면 중단된 prepare 가 남긴 그 디렉터리가 카운터에 안 잡히고, 다음 런이
|
|
386
|
+
같은 seq 를 배정받아 남은 스냅샷 위에 착지한다. digest 가 다르면
|
|
387
|
+
`run._snapshot_duty_catalog` 이 매번 같은 자리에서 PrepareError 를 내므로,
|
|
388
|
+
잔재를 손으로 치우기 전까지 그 task 의 prepare 가 영구히 막힌다.
|
|
379
389
|
"""
|
|
380
|
-
pattern = re.compile(
|
|
390
|
+
pattern = re.compile(
|
|
391
|
+
rf"-{re.escape(task_type_segment)}-(\d{{3,}})(?:\.[^.]+)?$"
|
|
392
|
+
)
|
|
381
393
|
max_seq = 0
|
|
382
394
|
if run_seq_dir.is_dir():
|
|
383
395
|
for entry in os.listdir(run_seq_dir):
|
|
384
|
-
full = run_seq_dir / entry
|
|
385
|
-
if not full.is_file():
|
|
386
|
-
continue
|
|
387
396
|
m = pattern.search(entry)
|
|
388
397
|
if m:
|
|
389
398
|
n = int(m.group(1))
|
|
@@ -404,7 +413,7 @@ def _rel(project_root: Path, target: Path) -> str:
|
|
|
404
413
|
def resolve_under_root(
|
|
405
414
|
project_root: str | Path | None, rel: str | None
|
|
406
415
|
) -> Path | None:
|
|
407
|
-
"""글로벌 인덱스의 신뢰 불가 상대경로 필드(runDirRel/
|
|
416
|
+
"""글로벌 인덱스의 신뢰 불가 상대경로 필드(runDirRel/finalReportRecordRel/
|
|
408
417
|
teamStatePath 등)를 project_root 기준 절대경로로 푼다(_rel 의 역).
|
|
409
418
|
|
|
410
419
|
이 필드들은 신뢰할 수 없어 절대경로나 '..' 가 들어오면 결합 결과가
|
|
@@ -538,7 +547,7 @@ def compute_run_paths(
|
|
|
538
547
|
codex_worker_prompt = run_prompts / f"codex-worker-prompt{suffixes['prompts']}.md"
|
|
539
548
|
antigravity_worker_prompt = run_prompts / f"antigravity-worker-prompt{suffixes['prompts']}.md"
|
|
540
549
|
report_writer_worker_prompt = run_prompts / f"report-writer-worker-prompt{suffixes['prompts']}.md"
|
|
541
|
-
final_report = run_reports / f"final-report{suffixes['reports']}.
|
|
550
|
+
final_report = run_reports / f"final-report{suffixes['reports']}.data.json"
|
|
542
551
|
final_status = run_status / f"final{suffixes['status']}.status"
|
|
543
552
|
team_state = run_state / f"team-state{suffixes['state']}.json"
|
|
544
553
|
active_run_context = run_state / f"active-run-context{suffixes['state']}.json"
|
|
@@ -712,7 +721,7 @@ def compute_run_paths(
|
|
|
712
721
|
("CODEX_WORKER_PROMPT_RELATIVE_PATH", codex_worker_prompt),
|
|
713
722
|
("ANTIGRAVITY_WORKER_PROMPT_RELATIVE_PATH", antigravity_worker_prompt),
|
|
714
723
|
("REPORT_WRITER_WORKER_PROMPT_RELATIVE_PATH", report_writer_worker_prompt),
|
|
715
|
-
("
|
|
724
|
+
("FINAL_REPORT_RECORD_RELATIVE_PATH", final_report),
|
|
716
725
|
("FINAL_STATUS_RELATIVE_PATH", final_status),
|
|
717
726
|
("TEAM_STATE_RELATIVE_PATH", team_state),
|
|
718
727
|
("ACTIVE_RUN_CONTEXT_RELATIVE_PATH", active_run_context),
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
원장) 과 approved-plan(final-report) 이 모두 이 루트 기준으로 놓인다.
|
|
5
5
|
|
|
6
6
|
두 진입점이 같은 layout 규칙을 공유한다:
|
|
7
|
-
- final-verification 은 사용자가 넘긴 approved-plan
|
|
8
|
-
(``<plan_run_root>/reports/final-report
|
|
7
|
+
- final-verification 은 사용자가 넘긴 approved-plan 정본 경로에서 역산한다
|
|
8
|
+
(``<plan_run_root>/reports/final-report-....data.json`` → ``parents[1]``).
|
|
9
9
|
- container 는 approved-plan 입력이 없으므로 task-key 로 planning run 디렉터리를
|
|
10
10
|
스캔해 latest done run 을 고른다.
|
|
11
11
|
"""
|
|
@@ -17,7 +17,9 @@ from typing import NamedTuple
|
|
|
17
17
|
|
|
18
18
|
from .stage_targets import PrepareError
|
|
19
19
|
|
|
20
|
-
_FINAL_REPORT_RE = re.compile(
|
|
20
|
+
_FINAL_REPORT_RE = re.compile(
|
|
21
|
+
r"final-report-implementation-planning-(\d+)\.data\.json$"
|
|
22
|
+
)
|
|
21
23
|
|
|
22
24
|
|
|
23
25
|
class PlanRun(NamedTuple):
|
|
@@ -29,7 +31,9 @@ class PlanRun(NamedTuple):
|
|
|
29
31
|
def list_implementation_planning_reports(reports_dir: Path) -> list[Path]:
|
|
30
32
|
"""Return numbered implementation-planning reports in latest-first order."""
|
|
31
33
|
numbered: list[tuple[int, Path]] = []
|
|
32
|
-
for report in reports_dir.glob(
|
|
34
|
+
for report in reports_dir.glob(
|
|
35
|
+
"final-report-implementation-planning-*.data.json"
|
|
36
|
+
):
|
|
33
37
|
match = _FINAL_REPORT_RE.fullmatch(report.name)
|
|
34
38
|
if match:
|
|
35
39
|
numbered.append((int(match.group(1)), report))
|
|
@@ -40,7 +44,7 @@ def list_implementation_planning_reports(reports_dir: Path) -> list[Path]:
|
|
|
40
44
|
def plan_run_root_from_approved_plan(approved_plan_path: str | Path) -> Path:
|
|
41
45
|
"""approved-plan(final-report) 경로에서 plan_run_root 를 역산한다.
|
|
42
46
|
|
|
43
|
-
|
|
47
|
+
정본은 ``<plan_run_root>/reports/final-report-....data.json`` 에 놓이므로
|
|
44
48
|
``parents[1]`` 이 run-root 다. run.py(final-verification) 와 container 가
|
|
45
49
|
이 한 규칙을 공유한다."""
|
|
46
50
|
return Path(approved_plan_path).resolve().parents[1]
|
|
@@ -18,6 +18,7 @@ from okstra_ctl.incremental_scope import preview_link_availability_for_report
|
|
|
18
18
|
from okstra_ctl.paths import task_timeline_file
|
|
19
19
|
from okstra_project import read_task_key
|
|
20
20
|
from okstra_ctl.run_context import dir_flock
|
|
21
|
+
from okstra_ctl.final_report_paths import timeline_report_record_rel
|
|
21
22
|
from okstra_ctl.task_target import resolve_task_root, project_rel
|
|
22
23
|
|
|
23
24
|
NOTE_KINDS = ("verification-evidence", "decision-draft", "analysis-note")
|
|
@@ -51,7 +52,7 @@ def _latest_planning_report(runs: list[dict], project_root: Path) -> Path | None
|
|
|
51
52
|
continue
|
|
52
53
|
if run.get("taskType") != "implementation-planning":
|
|
53
54
|
continue
|
|
54
|
-
relative =
|
|
55
|
+
relative = timeline_report_record_rel(run)
|
|
55
56
|
if not relative:
|
|
56
57
|
continue
|
|
57
58
|
report = project_root / relative
|
|
@@ -112,7 +113,7 @@ def assemble_recap(task_root: Path, project_root: Path) -> dict:
|
|
|
112
113
|
"nextRecommendedPhase": next_phase.promote(
|
|
113
114
|
snap.get("nextRecommendedPhase")
|
|
114
115
|
),
|
|
115
|
-
"
|
|
116
|
+
"reportRecordPath": timeline_report_record_rel(run),
|
|
116
117
|
})
|
|
117
118
|
prev_phase = cur
|
|
118
119
|
# latestPhaseStates 는 항상 가장 최근 run 의 상태를 반영해야 한다.
|
|
@@ -135,7 +135,9 @@ def _reconcile_active_locked(home: Path, *,
|
|
|
135
135
|
survivors.append(row)
|
|
136
136
|
continue
|
|
137
137
|
project_root = Path(row.get("projectRoot", ""))
|
|
138
|
-
|
|
138
|
+
from .final_report_paths import index_report_record_rel
|
|
139
|
+
|
|
140
|
+
final_rel = index_report_record_rel(row)
|
|
139
141
|
final_abs = project_root / final_rel if final_rel else None
|
|
140
142
|
if final_abs and final_abs.is_file():
|
|
141
143
|
validation_status = _read_run_manifest_validation(
|
|
@@ -591,7 +591,7 @@ def _active_run(ctx: dict) -> dict:
|
|
|
591
591
|
"runManifestPath": ctx.get("RUN_MANIFEST_RELATIVE_PATH", ""),
|
|
592
592
|
"teamStatePath": ctx.get("TEAM_STATE_RELATIVE_PATH", ""),
|
|
593
593
|
"promptSnapshotPath": ctx.get("RUN_PROMPT_SNAPSHOT_RELATIVE_PATH", ""),
|
|
594
|
-
"
|
|
594
|
+
"finalReportRecordPath": ctx.get("FINAL_REPORT_RECORD_RELATIVE_PATH", ""),
|
|
595
595
|
"convergenceStatePath": ctx.get("CONVERGENCE_STATE_RELATIVE_PATH", ""),
|
|
596
596
|
"finalStatusPath": ctx.get("FINAL_STATUS_RELATIVE_PATH", ""),
|
|
597
597
|
"validatorScriptPath": ctx.get("RUN_VALIDATOR_RELATIVE_PATH", ""),
|
|
@@ -795,7 +795,7 @@ def render_team_state(team_state_path: str, ctx: dict) -> None:
|
|
|
795
795
|
"artifacts": {
|
|
796
796
|
"leadPromptSnapshotPath": ctx.get("RUN_PROMPT_SNAPSHOT_RELATIVE_PATH", ""),
|
|
797
797
|
"workerPromptsDirectoryPath": ctx.get("RUN_PROMPTS_RELATIVE_PATH", ""),
|
|
798
|
-
"
|
|
798
|
+
"finalReportRecordPath": ctx.get("FINAL_REPORT_RECORD_RELATIVE_PATH", ""),
|
|
799
799
|
"finalStatusPath": ctx.get("FINAL_STATUS_RELATIVE_PATH", ""),
|
|
800
800
|
"workerResultsDirectoryPath": ctx.get("WORKER_RESULTS_RELATIVE_PATH", ""),
|
|
801
801
|
},
|
|
@@ -952,7 +952,7 @@ def render_task_catalog_discovery(output_path: str, ctx: dict) -> None:
|
|
|
952
952
|
"latestTeamStatePath": s(latest_run, "teamStatePath"),
|
|
953
953
|
"latestRunStatus": s(manifest, "latestRunStatus")
|
|
954
954
|
or s(latest_run, "status"),
|
|
955
|
-
"
|
|
955
|
+
"latestReportRecordPath": s(manifest, "latestReportRecordPath")
|
|
956
956
|
or s(latest_run, "reportPath"),
|
|
957
957
|
"latestResumeCommandPath": s(manifest, "latestResumeCommandPath")
|
|
958
958
|
or s(latest_run, "resumeCommandPath"),
|
|
@@ -1017,8 +1017,8 @@ def render_latest_task_discovery(output_path: str, ctx: dict) -> None:
|
|
|
1017
1017
|
"latestRunPromptsPath": ctx.get("RUN_PROMPTS_RELATIVE_PATH", ""),
|
|
1018
1018
|
"latestPromptSnapshotPath": ctx.get("RUN_PROMPT_SNAPSHOT_RELATIVE_PATH", ""),
|
|
1019
1019
|
"latestTeamStatePath": ctx.get("TEAM_STATE_RELATIVE_PATH", ""),
|
|
1020
|
-
"
|
|
1021
|
-
"
|
|
1020
|
+
"latestReportRecordPath": ctx.get("LATEST_REPORT_RECORD_RELATIVE_PATH", ""),
|
|
1021
|
+
"expectedReportRecordPath": ctx.get("FINAL_REPORT_RECORD_RELATIVE_PATH", ""),
|
|
1022
1022
|
"expectedStatusPath": ctx.get("FINAL_STATUS_RELATIVE_PATH", ""),
|
|
1023
1023
|
"latestResumeCommandPath": ctx.get("CLAUDE_RESUME_COMMAND_RELATIVE_PATH", ""),
|
|
1024
1024
|
"historyTimelinePath": ctx.get("TIMELINE_RELATIVE_PATH", ""),
|
|
@@ -1297,8 +1297,8 @@ def _derive_latest_pointers(existing: dict, ctx: dict, current_report_relative:
|
|
|
1297
1297
|
"latestTeamStatePath": existing_checkpoint.get(
|
|
1298
1298
|
"latestTeamStatePath", existing.get("teamStatePath", "")
|
|
1299
1299
|
),
|
|
1300
|
-
"
|
|
1301
|
-
"
|
|
1300
|
+
"latestReportRecordPath": existing_checkpoint.get(
|
|
1301
|
+
"latestReportRecordPath", existing.get("latestReportRecordPath", "")
|
|
1302
1302
|
),
|
|
1303
1303
|
"latestResumeCommandPath": existing_checkpoint.get(
|
|
1304
1304
|
"latestResumeCommandPath", existing.get("latestResumeCommandPath", "")
|
|
@@ -1310,7 +1310,7 @@ def _derive_latest_pointers(existing: dict, ctx: dict, current_report_relative:
|
|
|
1310
1310
|
or ctx.get("CURRENT_RUN_STATUS", ""),
|
|
1311
1311
|
"latestRunPromptsPath": existing.get("latestRunPromptsPath", "")
|
|
1312
1312
|
or ctx.get("RUN_PROMPTS_RELATIVE_PATH", ""),
|
|
1313
|
-
"
|
|
1313
|
+
"latestReportRecordPath": existing.get("latestReportRecordPath", "")
|
|
1314
1314
|
or current_report_relative,
|
|
1315
1315
|
"latestTeamStatePath": existing.get("teamStatePath", "")
|
|
1316
1316
|
or ctx.get("TEAM_STATE_RELATIVE_PATH", ""),
|
|
@@ -1324,14 +1324,14 @@ def _derive_latest_pointers(existing: dict, ctx: dict, current_report_relative:
|
|
|
1324
1324
|
"latestRunPath": ctx.get("LATEST_RUN_RELATIVE_PATH", ""),
|
|
1325
1325
|
"latestRunManifestPath": ctx.get("RUN_MANIFEST_RELATIVE_PATH", ""),
|
|
1326
1326
|
"latestTeamStatePath": ctx.get("TEAM_STATE_RELATIVE_PATH", ""),
|
|
1327
|
-
"
|
|
1327
|
+
"latestReportRecordPath": current_report_relative,
|
|
1328
1328
|
"latestResumeCommandPath": ctx.get("CLAUDE_RESUME_COMMAND_RELATIVE_PATH", ""),
|
|
1329
1329
|
},
|
|
1330
1330
|
"latestRunPath": ctx.get("LATEST_RUN_RELATIVE_PATH", ""),
|
|
1331
1331
|
"latestRunStatus": ctx.get("CURRENT_RUN_STATUS", ""),
|
|
1332
1332
|
"latestRunPromptsPath": ctx.get("RUN_PROMPTS_RELATIVE_PATH", ""),
|
|
1333
|
-
"
|
|
1334
|
-
or existing.get("
|
|
1333
|
+
"latestReportRecordPath": current_report_relative
|
|
1334
|
+
or existing.get("latestReportRecordPath", ""),
|
|
1335
1335
|
"latestTeamStatePath": ctx.get("TEAM_STATE_RELATIVE_PATH", ""),
|
|
1336
1336
|
"latestResumeCommandPath": ctx.get("CLAUDE_RESUME_COMMAND_RELATIVE_PATH", "")
|
|
1337
1337
|
or existing.get("latestResumeCommandPath", ""),
|
|
@@ -1357,8 +1357,8 @@ def render_task_manifest(manifest_path: str, ctx: dict) -> None:
|
|
|
1357
1357
|
catalog[item]["role"] for item in reviewers
|
|
1358
1358
|
]
|
|
1359
1359
|
related_tasks = json.loads(ctx.get("RELATED_TASKS_JSON", "[]"))
|
|
1360
|
-
current_report_relative = ctx.get("
|
|
1361
|
-
"
|
|
1360
|
+
current_report_relative = ctx.get("LATEST_REPORT_RECORD_RELATIVE_PATH") or ctx.get(
|
|
1361
|
+
"FINAL_REPORT_RECORD_RELATIVE_PATH", ""
|
|
1362
1362
|
)
|
|
1363
1363
|
existing_workflow = (
|
|
1364
1364
|
existing.get("workflow", {})
|
|
@@ -1385,7 +1385,7 @@ def render_task_manifest(manifest_path: str, ctx: dict) -> None:
|
|
|
1385
1385
|
latest_run_relative = pointers["latestRunPath"]
|
|
1386
1386
|
latest_run_status = pointers["latestRunStatus"]
|
|
1387
1387
|
latest_run_prompts_relative = pointers["latestRunPromptsPath"]
|
|
1388
|
-
latest_report_relative = pointers["
|
|
1388
|
+
latest_report_relative = pointers["latestReportRecordPath"]
|
|
1389
1389
|
latest_team_state_relative = pointers["latestTeamStatePath"]
|
|
1390
1390
|
latest_resume_command_relative = pointers["latestResumeCommandPath"]
|
|
1391
1391
|
convergence_block = _build_convergence_block(ctx)
|
|
@@ -1434,7 +1434,7 @@ def render_task_manifest(manifest_path: str, ctx: dict) -> None:
|
|
|
1434
1434
|
"latestRunPath": latest_run_relative,
|
|
1435
1435
|
"latestRunStatus": latest_run_status,
|
|
1436
1436
|
"latestRunPromptsPath": latest_run_prompts_relative,
|
|
1437
|
-
"
|
|
1437
|
+
"latestReportRecordPath": latest_report_relative,
|
|
1438
1438
|
"latestResumeCommandPath": latest_resume_command_relative,
|
|
1439
1439
|
"teamStatePath": latest_team_state_relative,
|
|
1440
1440
|
"fixCycles": fix_cycles.summarize(
|
|
@@ -1762,7 +1762,7 @@ def render_run_manifest(run_manifest_path: str, ctx: dict) -> None:
|
|
|
1762
1762
|
),
|
|
1763
1763
|
"workerPromptsDirectoryPath": ctx.get("RUN_PROMPTS_RELATIVE_PATH", ""),
|
|
1764
1764
|
"workerPromptPathByWorkerId": worker_prompt_paths,
|
|
1765
|
-
"
|
|
1765
|
+
"expectedReportRecordPath": ctx.get("FINAL_REPORT_RECORD_RELATIVE_PATH", ""),
|
|
1766
1766
|
"expectedStatusPath": ctx.get("FINAL_STATUS_RELATIVE_PATH", ""),
|
|
1767
1767
|
"teamStatePath": ctx.get("TEAM_STATE_RELATIVE_PATH", ""),
|
|
1768
1768
|
"activeRunContextPath": ctx.get("ACTIVE_RUN_CONTEXT_RELATIVE_PATH", ""),
|
|
@@ -1903,7 +1903,7 @@ _IMMUTABLE_INVOCATION_KEYS = (
|
|
|
1903
1903
|
"workerPromptsDirectoryPath",
|
|
1904
1904
|
"workerPromptPathByWorkerId",
|
|
1905
1905
|
"workerResultsDirectoryPath",
|
|
1906
|
-
"
|
|
1906
|
+
"expectedReportRecordPath",
|
|
1907
1907
|
"reportTemplatePath",
|
|
1908
1908
|
)
|
|
1909
1909
|
|
|
@@ -2091,8 +2091,8 @@ def render_timeline(timeline_path: str, ctx: dict) -> None:
|
|
|
2091
2091
|
"workerPromptPathByWorkerId": {
|
|
2092
2092
|
item: catalog[item]["promptPath"] for item in reviewers
|
|
2093
2093
|
},
|
|
2094
|
-
"
|
|
2095
|
-
or ctx.get("
|
|
2094
|
+
"reportRecordPath": ctx.get("LATEST_REPORT_RECORD_RELATIVE_PATH")
|
|
2095
|
+
or ctx.get("FINAL_REPORT_RECORD_RELATIVE_PATH", ""),
|
|
2096
2096
|
"teamStatePath": ctx.get("TEAM_STATE_RELATIVE_PATH", ""),
|
|
2097
2097
|
"resumeCommandPath": ctx.get("CLAUDE_RESUME_COMMAND_RELATIVE_PATH", ""),
|
|
2098
2098
|
"relatedTasks": json.loads(ctx.get("RELATED_TASKS_JSON", "[]")),
|
|
@@ -2172,7 +2172,7 @@ def render_task_index(template_path: str, output_path: str, ctx: dict) -> None:
|
|
|
2172
2172
|
f"- Label: `{checkpoint.get('label', 'unknown')}`",
|
|
2173
2173
|
f"- Run manifest: `{checkpoint.get('latestRunManifestPath', ctx.get('RUN_MANIFEST_RELATIVE_PATH', ''))}`",
|
|
2174
2174
|
f"- Team state: `{checkpoint.get('latestTeamStatePath', ctx.get('TEAM_STATE_RELATIVE_PATH', ''))}`",
|
|
2175
|
-
f"- Report: `{checkpoint.get('
|
|
2175
|
+
f"- Report: `{checkpoint.get('latestReportRecordPath', task_manifest.get('latestReportRecordPath', '--')) or '--'}`",
|
|
2176
2176
|
f"- Resume command: `{checkpoint.get('latestResumeCommandPath', task_manifest.get('latestResumeCommandPath', '--')) or '--'}`",
|
|
2177
2177
|
]
|
|
2178
2178
|
rc = (
|
|
@@ -2236,8 +2236,8 @@ def render_task_index(template_path: str, output_path: str, ctx: dict) -> None:
|
|
|
2236
2236
|
"{{LATEST_RUN_RELATIVE_PATH}}": task_manifest.get(
|
|
2237
2237
|
"latestRunPath", ctx.get("LATEST_RUN_RELATIVE_PATH", "")
|
|
2238
2238
|
),
|
|
2239
|
-
"{{
|
|
2240
|
-
"
|
|
2239
|
+
"{{LATEST_REPORT_RECORD_RELATIVE_PATH}}": task_manifest.get(
|
|
2240
|
+
"latestReportRecordPath", ctx.get("LATEST_REPORT_RECORD_RELATIVE_PATH", "")
|
|
2241
2241
|
),
|
|
2242
2242
|
"{{TEAM_STATE_RELATIVE_PATH}}": task_manifest.get(
|
|
2243
2243
|
"teamStatePath", ctx.get("TEAM_STATE_RELATIVE_PATH", "")
|