okstra 0.137.0 → 0.138.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.md +3 -2
- package/docs/contributor-change-matrix.md +1 -1
- package/docs/project-structure-overview.md +24 -2
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/workers/antigravity-worker.md +1 -1
- package/runtime/agents/workers/codex-worker.md +1 -1
- package/runtime/prompts/lead/adapters/claude-code.md +1 -0
- package/runtime/prompts/lead/adapters/codex.md +1 -0
- package/runtime/prompts/lead/adapters/external.md +1 -0
- package/runtime/prompts/lead/report-writer.md +11 -10
- package/runtime/prompts/lead/team-contract.md +4 -2
- package/runtime/prompts/profiles/_implementation-diff-review.md +1 -1
- package/runtime/prompts/profiles/_implementation-executor.md +2 -2
- package/runtime/prompts/profiles/_implementation-self-check.md +1 -1
- package/runtime/python/okstra_ctl/codex_dispatch.py +158 -365
- package/runtime/python/okstra_ctl/dispatch_core.py +118 -138
- package/runtime/python/okstra_ctl/initial_prompt_materialization.py +1053 -0
- package/runtime/python/okstra_ctl/render.py +9 -4
- package/runtime/python/okstra_ctl/run.py +40 -89
- package/runtime/python/okstra_ctl/stage_targets.py +232 -46
- package/runtime/python/okstra_ctl/wizard.py +2 -2
- package/runtime/python/okstra_ctl/worker_prompt_contract.py +73 -0
- package/runtime/python/okstra_ctl/worktree.py +37 -29
- package/runtime/validators/lib/fixtures.sh +2 -0
|
@@ -22,6 +22,13 @@ from .final_report_paths import (
|
|
|
22
22
|
final_report_data_path as _final_report_data_path,
|
|
23
23
|
final_report_markdown_path as _final_report_markdown_path,
|
|
24
24
|
)
|
|
25
|
+
from .initial_prompt_materialization import (
|
|
26
|
+
InitialPromptMaterializationError,
|
|
27
|
+
InitialPromptMaterializationRequest,
|
|
28
|
+
InitialPromptWorkerRequest,
|
|
29
|
+
PromptDeliveryMode,
|
|
30
|
+
materialize_initial_prompts,
|
|
31
|
+
)
|
|
25
32
|
from .lead_events import LeadEvent, append_lead_event
|
|
26
33
|
from .dispatch_state import (
|
|
27
34
|
BACKEND_CLI_WRAPPER,
|
|
@@ -40,8 +47,6 @@ from .dispatch_state import (
|
|
|
40
47
|
string_list as _string_list,
|
|
41
48
|
string_value as _string_value,
|
|
42
49
|
utc_now as _utc_now,
|
|
43
|
-
validate_initial_prompts as _validate_initial_prompts,
|
|
44
|
-
worker_prompt_path as _worker_prompt_path,
|
|
45
50
|
worker_state as _worker_state,
|
|
46
51
|
worktree_path as _worktree_path,
|
|
47
52
|
write_json as _write_json,
|
|
@@ -51,28 +56,12 @@ from .report_finalize import (
|
|
|
51
56
|
STEP_VALIDATE_RUN,
|
|
52
57
|
FinalizeContext,
|
|
53
58
|
FinalizeError,
|
|
54
|
-
project_relative_path as _project_relative_path,
|
|
55
59
|
resolve_optional_path as _resolve_optional_path,
|
|
56
60
|
run_finalize,
|
|
57
61
|
run_seq as _run_seq,
|
|
58
62
|
)
|
|
59
63
|
from .wrapper_status import status_path_for_prompt
|
|
60
|
-
from .
|
|
61
|
-
from .worker_prompt_headers import WorkerPromptHeaderError, worker_prompt_headers
|
|
62
|
-
from .worker_prompt_contract import PromptRecord, validate_initial_prompt_records
|
|
63
|
-
from .worker_prompt_body import (
|
|
64
|
-
analysis_input_lines as _analysis_input_lines,
|
|
65
|
-
analysis_prompt_body as _analysis_prompt_body,
|
|
66
|
-
existing_input_lines as _existing_input_lines,
|
|
67
|
-
instruction_path as _instruction_path,
|
|
68
|
-
mcp_pointer_line as _mcp_pointer_line,
|
|
69
|
-
report_writer_prompt_body,
|
|
70
|
-
REPORT_WRITER_WORKER_ID,
|
|
71
|
-
)
|
|
72
|
-
from .worker_prompt_policy import (
|
|
73
|
-
PromptPlan,
|
|
74
|
-
resolve_prompt_plan_for_manifest,
|
|
75
|
-
)
|
|
64
|
+
from .worker_prompt_body import REPORT_WRITER_WORKER_ID
|
|
76
65
|
|
|
77
66
|
|
|
78
67
|
SUPPORTED_CLI_WORKERS = {
|
|
@@ -109,6 +98,18 @@ class DispatchPlan:
|
|
|
109
98
|
}
|
|
110
99
|
|
|
111
100
|
|
|
101
|
+
@dataclass(frozen=True)
|
|
102
|
+
class _DispatchInputs:
|
|
103
|
+
project_root: Path
|
|
104
|
+
manifest_path: Path
|
|
105
|
+
manifest: Mapping[str, Any]
|
|
106
|
+
selected_workers: tuple[str, ...]
|
|
107
|
+
team_state_path: Path
|
|
108
|
+
team_state: Mapping[str, Any]
|
|
109
|
+
active_context: Mapping[str, Any]
|
|
110
|
+
lead_events_path: Path
|
|
111
|
+
|
|
112
|
+
|
|
112
113
|
def build_dispatch_plan(
|
|
113
114
|
*,
|
|
114
115
|
project_root: Path,
|
|
@@ -120,15 +121,60 @@ def build_dispatch_plan(
|
|
|
120
121
|
enable_codex_report_writer: bool = False,
|
|
121
122
|
report_writer_codex_model: str = "",
|
|
122
123
|
) -> DispatchPlan:
|
|
124
|
+
inputs = _load_dispatch_inputs(
|
|
125
|
+
project_root,
|
|
126
|
+
run_manifest_path,
|
|
127
|
+
requested_workers,
|
|
128
|
+
enable_codex_report_writer=enable_codex_report_writer,
|
|
129
|
+
)
|
|
130
|
+
worker_requests, prompt_paths = _materialize_selected_worker_prompts(
|
|
131
|
+
project_root=inputs.project_root,
|
|
132
|
+
manifest_path=inputs.manifest_path,
|
|
133
|
+
workspace_root=workspace_root,
|
|
134
|
+
team_state=inputs.team_state,
|
|
135
|
+
selected_workers=inputs.selected_workers,
|
|
136
|
+
report_writer_codex_model=report_writer_codex_model,
|
|
137
|
+
)
|
|
138
|
+
workers = _build_selected_worker_dispatches(
|
|
139
|
+
project_root=inputs.project_root,
|
|
140
|
+
manifest=inputs.manifest,
|
|
141
|
+
team_state=inputs.team_state,
|
|
142
|
+
active_context=inputs.active_context,
|
|
143
|
+
workspace_root=workspace_root,
|
|
144
|
+
okstra_bin=okstra_bin,
|
|
145
|
+
idle_timeout_seconds=idle_timeout_seconds,
|
|
146
|
+
selected_workers=inputs.selected_workers,
|
|
147
|
+
worker_requests=worker_requests,
|
|
148
|
+
prompt_paths=prompt_paths,
|
|
149
|
+
)
|
|
150
|
+
return DispatchPlan(
|
|
151
|
+
project_root=inputs.project_root,
|
|
152
|
+
workspace_root=workspace_root,
|
|
153
|
+
manifest_path=inputs.manifest_path,
|
|
154
|
+
team_state_path=inputs.team_state_path,
|
|
155
|
+
lead_events_path=inputs.lead_events_path,
|
|
156
|
+
manifest=inputs.manifest,
|
|
157
|
+
workers=workers,
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _load_dispatch_inputs(
|
|
162
|
+
project_root: Path,
|
|
163
|
+
run_manifest_path: Path,
|
|
164
|
+
requested_workers: Sequence[str],
|
|
165
|
+
*,
|
|
166
|
+
enable_codex_report_writer: bool,
|
|
167
|
+
) -> _DispatchInputs:
|
|
123
168
|
project_root = project_root.resolve()
|
|
124
169
|
manifest_path = _resolve_project_path(project_root, str(run_manifest_path))
|
|
125
170
|
manifest = _load_json_object(manifest_path, "run manifest")
|
|
126
171
|
_validate_codex_manifest(manifest, manifest_path)
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
172
|
+
selected_workers = tuple(
|
|
173
|
+
_select_workers(
|
|
174
|
+
manifest,
|
|
175
|
+
requested_workers,
|
|
176
|
+
enable_codex_report_writer=enable_codex_report_writer,
|
|
177
|
+
)
|
|
132
178
|
)
|
|
133
179
|
team_state_path = _resolve_required_path(project_root, manifest, "teamStatePath")
|
|
134
180
|
team_state = _load_json_object(team_state_path, "team-state")
|
|
@@ -145,17 +191,67 @@ def build_dispatch_plan(
|
|
|
145
191
|
active_context = _load_optional_json_object(
|
|
146
192
|
_resolve_optional_path(project_root, manifest.get("activeRunContextPath"))
|
|
147
193
|
)
|
|
148
|
-
|
|
149
|
-
_materialize_missing_worker_prompts(
|
|
194
|
+
return _DispatchInputs(
|
|
150
195
|
project_root=project_root,
|
|
196
|
+
manifest_path=manifest_path,
|
|
151
197
|
manifest=manifest,
|
|
198
|
+
selected_workers=selected_workers,
|
|
199
|
+
team_state_path=team_state_path,
|
|
152
200
|
team_state=team_state,
|
|
153
201
|
active_context=active_context,
|
|
154
|
-
|
|
155
|
-
workspace_root=workspace_root,
|
|
156
|
-
report_writer_codex_model=report_writer_codex_model,
|
|
202
|
+
lead_events_path=_resolve_required_path(project_root, manifest, "leadEventsPath"),
|
|
157
203
|
)
|
|
158
|
-
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def _materialize_selected_worker_prompts(
|
|
207
|
+
*,
|
|
208
|
+
project_root: Path,
|
|
209
|
+
manifest_path: Path,
|
|
210
|
+
workspace_root: Path,
|
|
211
|
+
team_state: Mapping[str, Any],
|
|
212
|
+
selected_workers: Sequence[str],
|
|
213
|
+
report_writer_codex_model: str,
|
|
214
|
+
) -> tuple[tuple[InitialPromptWorkerRequest, ...], tuple[Path, ...]]:
|
|
215
|
+
worker_requests = tuple(
|
|
216
|
+
InitialPromptWorkerRequest(
|
|
217
|
+
worker_id=worker_id,
|
|
218
|
+
model=_requested_worker_model(
|
|
219
|
+
worker_id,
|
|
220
|
+
team_state,
|
|
221
|
+
report_writer_codex_model,
|
|
222
|
+
),
|
|
223
|
+
)
|
|
224
|
+
for worker_id in selected_workers
|
|
225
|
+
)
|
|
226
|
+
try:
|
|
227
|
+
prompt_paths = materialize_initial_prompts(
|
|
228
|
+
InitialPromptMaterializationRequest(
|
|
229
|
+
project_root=project_root,
|
|
230
|
+
run_manifest_path=manifest_path,
|
|
231
|
+
runtime_root=workspace_root,
|
|
232
|
+
delivery_mode=PromptDeliveryMode.EAGER_INCLUDE,
|
|
233
|
+
workers=worker_requests,
|
|
234
|
+
)
|
|
235
|
+
)
|
|
236
|
+
except InitialPromptMaterializationError as exc:
|
|
237
|
+
raise DispatchError(f"{exc.reason}: {exc}") from exc
|
|
238
|
+
return worker_requests, prompt_paths
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def _build_selected_worker_dispatches(
|
|
242
|
+
*,
|
|
243
|
+
project_root: Path,
|
|
244
|
+
manifest: Mapping[str, Any],
|
|
245
|
+
team_state: Mapping[str, Any],
|
|
246
|
+
active_context: Mapping[str, Any],
|
|
247
|
+
workspace_root: Path,
|
|
248
|
+
okstra_bin: Path | None,
|
|
249
|
+
idle_timeout_seconds: int,
|
|
250
|
+
selected_workers: Sequence[str],
|
|
251
|
+
worker_requests: Sequence[InitialPromptWorkerRequest],
|
|
252
|
+
prompt_paths: Sequence[Path],
|
|
253
|
+
) -> tuple[WorkerJob, ...]:
|
|
254
|
+
return tuple(
|
|
159
255
|
_build_worker_dispatch(
|
|
160
256
|
worker_id=worker_id,
|
|
161
257
|
project_root=project_root,
|
|
@@ -165,19 +261,15 @@ def build_dispatch_plan(
|
|
|
165
261
|
workspace_root=workspace_root,
|
|
166
262
|
okstra_bin=okstra_bin,
|
|
167
263
|
idle_timeout_seconds=idle_timeout_seconds,
|
|
168
|
-
|
|
264
|
+
model_execution_value=worker_request.model,
|
|
265
|
+
prompt_path=prompt_path,
|
|
266
|
+
)
|
|
267
|
+
for worker_id, worker_request, prompt_path in zip(
|
|
268
|
+
selected_workers,
|
|
269
|
+
worker_requests,
|
|
270
|
+
prompt_paths,
|
|
271
|
+
strict=True,
|
|
169
272
|
)
|
|
170
|
-
for worker_id in selected_workers
|
|
171
|
-
)
|
|
172
|
-
_validate_initial_prompts(manifest, workers)
|
|
173
|
-
return DispatchPlan(
|
|
174
|
-
project_root=project_root,
|
|
175
|
-
workspace_root=workspace_root,
|
|
176
|
-
manifest_path=manifest_path,
|
|
177
|
-
team_state_path=team_state_path,
|
|
178
|
-
lead_events_path=lead_events_path,
|
|
179
|
-
manifest=manifest,
|
|
180
|
-
workers=workers,
|
|
181
273
|
)
|
|
182
274
|
|
|
183
275
|
|
|
@@ -357,146 +449,6 @@ def _parser() -> argparse.ArgumentParser:
|
|
|
357
449
|
return parser
|
|
358
450
|
|
|
359
451
|
|
|
360
|
-
def _materialize_missing_worker_prompts(
|
|
361
|
-
*,
|
|
362
|
-
project_root: Path,
|
|
363
|
-
manifest: Mapping[str, Any],
|
|
364
|
-
team_state: Mapping[str, Any],
|
|
365
|
-
active_context: Mapping[str, Any],
|
|
366
|
-
selected_workers: Sequence[str],
|
|
367
|
-
workspace_root: Path,
|
|
368
|
-
report_writer_codex_model: str,
|
|
369
|
-
) -> None:
|
|
370
|
-
for worker_id in selected_workers:
|
|
371
|
-
prompt_path = _resolve_project_path(
|
|
372
|
-
project_root, _worker_prompt_path(manifest, worker_id)
|
|
373
|
-
)
|
|
374
|
-
if prompt_path.is_file():
|
|
375
|
-
continue
|
|
376
|
-
prompt = _render_missing_worker_prompt(
|
|
377
|
-
project_root=project_root,
|
|
378
|
-
manifest=manifest,
|
|
379
|
-
team_state=team_state,
|
|
380
|
-
active_context=active_context,
|
|
381
|
-
worker_id=worker_id,
|
|
382
|
-
workspace_root=workspace_root,
|
|
383
|
-
report_writer_codex_model=report_writer_codex_model,
|
|
384
|
-
)
|
|
385
|
-
prompt_path.parent.mkdir(parents=True, exist_ok=True)
|
|
386
|
-
prompt_path.write_text(prompt, encoding="utf-8")
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
def _render_missing_worker_prompt(
|
|
390
|
-
*,
|
|
391
|
-
project_root: Path,
|
|
392
|
-
manifest: Mapping[str, Any],
|
|
393
|
-
team_state: Mapping[str, Any],
|
|
394
|
-
active_context: Mapping[str, Any],
|
|
395
|
-
worker_id: str,
|
|
396
|
-
workspace_root: Path,
|
|
397
|
-
report_writer_codex_model: str,
|
|
398
|
-
) -> str:
|
|
399
|
-
if worker_id == REPORT_WRITER_WORKER_ID:
|
|
400
|
-
return _render_missing_report_writer_prompt(
|
|
401
|
-
project_root=project_root,
|
|
402
|
-
manifest=manifest,
|
|
403
|
-
team_state=team_state,
|
|
404
|
-
active_context=active_context,
|
|
405
|
-
report_writer_codex_model=report_writer_codex_model,
|
|
406
|
-
)
|
|
407
|
-
return _render_missing_analysis_worker_prompt(
|
|
408
|
-
project_root=project_root,
|
|
409
|
-
manifest=manifest,
|
|
410
|
-
team_state=team_state,
|
|
411
|
-
active_context=active_context,
|
|
412
|
-
worker_id=worker_id,
|
|
413
|
-
workspace_root=workspace_root,
|
|
414
|
-
)
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
def _render_missing_analysis_worker_prompt(
|
|
418
|
-
*,
|
|
419
|
-
project_root: Path,
|
|
420
|
-
manifest: Mapping[str, Any],
|
|
421
|
-
team_state: Mapping[str, Any],
|
|
422
|
-
active_context: Mapping[str, Any],
|
|
423
|
-
worker_id: str,
|
|
424
|
-
workspace_root: Path,
|
|
425
|
-
) -> str:
|
|
426
|
-
worker_state = _worker_state(team_state, worker_id)
|
|
427
|
-
result_rel = _require_string(worker_state, "resultPath")
|
|
428
|
-
prompt_rel = _worker_prompt_path(manifest, worker_id)
|
|
429
|
-
model = _require_string(worker_state, "modelExecutionValue")
|
|
430
|
-
role = _analysis_pane_role(manifest, active_context, worker_id)
|
|
431
|
-
plan = _initial_prompt_plan(manifest, worker_id)
|
|
432
|
-
lines = _base_prompt_headers(
|
|
433
|
-
project_root=project_root,
|
|
434
|
-
manifest=manifest,
|
|
435
|
-
active_context=active_context,
|
|
436
|
-
worker_id=worker_id,
|
|
437
|
-
prompt_rel=prompt_rel,
|
|
438
|
-
result_rel=result_rel,
|
|
439
|
-
)
|
|
440
|
-
lines.extend(_worktree_headers(manifest, active_context, role))
|
|
441
|
-
lines.extend(
|
|
442
|
-
_analysis_prompt_body(
|
|
443
|
-
manifest,
|
|
444
|
-
active_context,
|
|
445
|
-
worker_id,
|
|
446
|
-
model,
|
|
447
|
-
role,
|
|
448
|
-
plan,
|
|
449
|
-
)
|
|
450
|
-
)
|
|
451
|
-
executor_tail = _implementation_executor_tail(
|
|
452
|
-
manifest,
|
|
453
|
-
active_context,
|
|
454
|
-
project_root,
|
|
455
|
-
workspace_root,
|
|
456
|
-
role,
|
|
457
|
-
)
|
|
458
|
-
if executor_tail:
|
|
459
|
-
lines.extend(["", executor_tail.rstrip()])
|
|
460
|
-
return "\n".join(lines).rstrip() + "\n"
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
def _render_missing_report_writer_prompt(
|
|
464
|
-
*,
|
|
465
|
-
project_root: Path,
|
|
466
|
-
manifest: Mapping[str, Any],
|
|
467
|
-
team_state: Mapping[str, Any],
|
|
468
|
-
active_context: Mapping[str, Any],
|
|
469
|
-
report_writer_codex_model: str,
|
|
470
|
-
) -> str:
|
|
471
|
-
worker_state = _worker_state(team_state, REPORT_WRITER_WORKER_ID)
|
|
472
|
-
markdown_path = _resolve_required_path(project_root, manifest, "expectedReportPath")
|
|
473
|
-
data_json_path = _final_report_data_path(markdown_path)
|
|
474
|
-
result_rel = _project_relative_path(project_root, data_json_path)
|
|
475
|
-
worker_result_rel = _require_string(worker_state, "resultPath")
|
|
476
|
-
model = (report_writer_codex_model or "").strip()
|
|
477
|
-
if not model:
|
|
478
|
-
raise DispatchError(
|
|
479
|
-
"Codex report-writer dispatch requires --report-writer-codex-model"
|
|
480
|
-
)
|
|
481
|
-
lines = _base_prompt_headers(
|
|
482
|
-
project_root=project_root,
|
|
483
|
-
manifest=manifest,
|
|
484
|
-
active_context=active_context,
|
|
485
|
-
worker_id=REPORT_WRITER_WORKER_ID,
|
|
486
|
-
prompt_rel=_worker_prompt_path(manifest, REPORT_WRITER_WORKER_ID),
|
|
487
|
-
result_rel=result_rel,
|
|
488
|
-
audit_source_rel=worker_result_rel,
|
|
489
|
-
)
|
|
490
|
-
lines.extend(report_writer_prompt_body(
|
|
491
|
-
manifest,
|
|
492
|
-
active_context,
|
|
493
|
-
team_state,
|
|
494
|
-
model,
|
|
495
|
-
resolve_report_language(project_root, manifest, active_context),
|
|
496
|
-
))
|
|
497
|
-
return "\n".join(lines).rstrip() + "\n"
|
|
498
|
-
|
|
499
|
-
|
|
500
452
|
def _build_worker_dispatch(
|
|
501
453
|
*,
|
|
502
454
|
worker_id: str,
|
|
@@ -507,7 +459,8 @@ def _build_worker_dispatch(
|
|
|
507
459
|
workspace_root: Path,
|
|
508
460
|
okstra_bin: Path | None,
|
|
509
461
|
idle_timeout_seconds: int,
|
|
510
|
-
|
|
462
|
+
model_execution_value: str,
|
|
463
|
+
prompt_path: Path,
|
|
511
464
|
) -> WorkerJob:
|
|
512
465
|
if worker_id == REPORT_WRITER_WORKER_ID:
|
|
513
466
|
return _build_report_writer_dispatch(
|
|
@@ -517,15 +470,13 @@ def _build_worker_dispatch(
|
|
|
517
470
|
workspace_root=workspace_root,
|
|
518
471
|
okstra_bin=okstra_bin,
|
|
519
472
|
idle_timeout_seconds=idle_timeout_seconds,
|
|
520
|
-
|
|
473
|
+
model_execution_value=model_execution_value,
|
|
474
|
+
prompt_path=prompt_path,
|
|
521
475
|
)
|
|
522
476
|
worker_state = _worker_state(team_state, worker_id)
|
|
523
|
-
prompt_rel = _worker_prompt_path(manifest, worker_id)
|
|
524
|
-
prompt_path = _resolve_project_path(project_root, prompt_rel)
|
|
525
477
|
result_path = _resolve_project_path(
|
|
526
478
|
project_root, _require_string(worker_state, "resultPath")
|
|
527
479
|
)
|
|
528
|
-
model_execution_value = _require_string(worker_state, "modelExecutionValue")
|
|
529
480
|
wrapper = _resolve_wrapper(worker_id, workspace_root, okstra_bin)
|
|
530
481
|
worktree_path = _worktree_path(manifest, active_context)
|
|
531
482
|
role = _pane_role(prompt_path)
|
|
@@ -555,17 +506,10 @@ def _build_report_writer_dispatch(
|
|
|
555
506
|
workspace_root: Path,
|
|
556
507
|
okstra_bin: Path | None,
|
|
557
508
|
idle_timeout_seconds: int,
|
|
558
|
-
|
|
509
|
+
model_execution_value: str,
|
|
510
|
+
prompt_path: Path,
|
|
559
511
|
) -> WorkerJob:
|
|
560
|
-
model_execution_value = (report_writer_codex_model or "").strip()
|
|
561
|
-
if not model_execution_value:
|
|
562
|
-
raise DispatchError(
|
|
563
|
-
"Codex report-writer dispatch requires --report-writer-codex-model"
|
|
564
|
-
)
|
|
565
512
|
worker_state = _worker_state(team_state, REPORT_WRITER_WORKER_ID)
|
|
566
|
-
prompt_path = _resolve_project_path(
|
|
567
|
-
project_root, _worker_prompt_path(manifest, REPORT_WRITER_WORKER_ID)
|
|
568
|
-
)
|
|
569
513
|
data_json_path = _final_report_data_path(
|
|
570
514
|
_resolve_required_path(project_root, manifest, "expectedReportPath")
|
|
571
515
|
)
|
|
@@ -573,7 +517,6 @@ def _build_report_writer_dispatch(
|
|
|
573
517
|
worker_result_path = _resolve_project_path(
|
|
574
518
|
project_root, _require_string(worker_state, "resultPath")
|
|
575
519
|
)
|
|
576
|
-
_replace_report_writer_model_line(prompt_path, model_execution_value)
|
|
577
520
|
return WorkerJob(
|
|
578
521
|
worker_id=REPORT_WRITER_WORKER_ID,
|
|
579
522
|
# codex 경로의 report-writer 는 claude 가 아니라 codex wrapper 로 돈다.
|
|
@@ -597,157 +540,6 @@ def _build_report_writer_dispatch(
|
|
|
597
540
|
)
|
|
598
541
|
|
|
599
542
|
|
|
600
|
-
def _base_prompt_headers(
|
|
601
|
-
*,
|
|
602
|
-
project_root: Path,
|
|
603
|
-
manifest: Mapping[str, Any],
|
|
604
|
-
active_context: Mapping[str, Any],
|
|
605
|
-
worker_id: str,
|
|
606
|
-
prompt_rel: str,
|
|
607
|
-
result_rel: str,
|
|
608
|
-
audit_source_rel: str | None = None,
|
|
609
|
-
) -> list[str]:
|
|
610
|
-
try:
|
|
611
|
-
return worker_prompt_headers(
|
|
612
|
-
project_root=project_root,
|
|
613
|
-
prompt_rel=prompt_rel,
|
|
614
|
-
result_rel=result_rel,
|
|
615
|
-
audit_source_rel=audit_source_rel,
|
|
616
|
-
worker_id=worker_id,
|
|
617
|
-
dispatch_kind="initial",
|
|
618
|
-
manifest=manifest,
|
|
619
|
-
active_context=active_context,
|
|
620
|
-
)
|
|
621
|
-
except WorkerPromptHeaderError as exc:
|
|
622
|
-
raise DispatchError(str(exc)) from exc
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
def _initial_prompt_plan(
|
|
626
|
-
manifest: Mapping[str, Any],
|
|
627
|
-
worker_id: str,
|
|
628
|
-
) -> PromptPlan:
|
|
629
|
-
try:
|
|
630
|
-
return resolve_prompt_plan_for_manifest(
|
|
631
|
-
manifest=manifest,
|
|
632
|
-
worker_id=worker_id,
|
|
633
|
-
dispatch_kind="initial",
|
|
634
|
-
)
|
|
635
|
-
except ValueError as exc:
|
|
636
|
-
raise DispatchError(str(exc)) from exc
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
def _analysis_pane_role(
|
|
640
|
-
manifest: Mapping[str, Any],
|
|
641
|
-
active_context: Mapping[str, Any],
|
|
642
|
-
worker_id: str,
|
|
643
|
-
) -> str:
|
|
644
|
-
if _require_string(manifest, "taskType") != "implementation":
|
|
645
|
-
return "worker"
|
|
646
|
-
if _executor_provider(manifest) != worker_id:
|
|
647
|
-
return "worker"
|
|
648
|
-
worktree = _worktree_path(manifest, active_context)
|
|
649
|
-
if not worktree:
|
|
650
|
-
raise DispatchError("implementation executor prompt generation requires a worktree path")
|
|
651
|
-
return "executor"
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
def _executor_provider(manifest: Mapping[str, Any]) -> str:
|
|
655
|
-
team_contract = manifest.get("teamContract")
|
|
656
|
-
if not isinstance(team_contract, Mapping):
|
|
657
|
-
return ""
|
|
658
|
-
executor = team_contract.get("executor")
|
|
659
|
-
if not isinstance(executor, Mapping):
|
|
660
|
-
return ""
|
|
661
|
-
return _string_value(executor.get("provider"))
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
def _worktree_headers(
|
|
665
|
-
manifest: Mapping[str, Any],
|
|
666
|
-
active_context: Mapping[str, Any],
|
|
667
|
-
role: str,
|
|
668
|
-
) -> list[str]:
|
|
669
|
-
task_type = _require_string(manifest, "taskType")
|
|
670
|
-
worktree = _worktree_path(manifest, active_context)
|
|
671
|
-
if task_type != "implementation" or not worktree:
|
|
672
|
-
return []
|
|
673
|
-
headers = [f"**Worktree:** {worktree}"]
|
|
674
|
-
if role == "executor":
|
|
675
|
-
headers.append(f"cwd for every mutating command: {worktree}")
|
|
676
|
-
return headers
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
def _implementation_executor_tail(
|
|
680
|
-
manifest: Mapping[str, Any],
|
|
681
|
-
active_context: Mapping[str, Any],
|
|
682
|
-
project_root: Path,
|
|
683
|
-
workspace_root: Path,
|
|
684
|
-
role: str,
|
|
685
|
-
) -> str:
|
|
686
|
-
if role != "executor" or _require_string(manifest, "taskType") != "implementation":
|
|
687
|
-
return ""
|
|
688
|
-
profiles = workspace_root / "prompts" / "profiles"
|
|
689
|
-
parts: list[str] = []
|
|
690
|
-
analysis_profile = _instruction_path(
|
|
691
|
-
manifest,
|
|
692
|
-
active_context,
|
|
693
|
-
"analysisProfilePath",
|
|
694
|
-
)
|
|
695
|
-
if not analysis_profile:
|
|
696
|
-
raise DispatchError("implementation executor profile path is missing")
|
|
697
|
-
executor_profile = (
|
|
698
|
-
_resolve_project_path(project_root, analysis_profile).parent
|
|
699
|
-
/ "implementation-executor.md"
|
|
700
|
-
)
|
|
701
|
-
if not executor_profile.is_file():
|
|
702
|
-
raise DispatchError(
|
|
703
|
-
f"resolved implementation executor profile not found: {executor_profile}"
|
|
704
|
-
)
|
|
705
|
-
parts.append(executor_profile.read_text(encoding="utf-8"))
|
|
706
|
-
preflight_path = profiles / "_coding-conventions-preflight.md"
|
|
707
|
-
if preflight_path.is_file():
|
|
708
|
-
parts.append(preflight_path.read_text(encoding="utf-8"))
|
|
709
|
-
else:
|
|
710
|
-
parts.append("# Coding-conventions preflight\n\nApply project-local conventions before editing.")
|
|
711
|
-
diff_review_path = profiles / "_implementation-diff-review.md"
|
|
712
|
-
if diff_review_path.is_file():
|
|
713
|
-
parts.append(diff_review_path.read_text(encoding="utf-8"))
|
|
714
|
-
selfcheck_path = profiles / "_implementation-self-check.md"
|
|
715
|
-
if selfcheck_path.is_file():
|
|
716
|
-
parts.append(selfcheck_path.read_text(encoding="utf-8"))
|
|
717
|
-
parts.extend(_clarification_response_section(manifest, active_context, project_root))
|
|
718
|
-
return "\n\n".join(parts)
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
def _clarification_response_section(
|
|
722
|
-
manifest: Mapping[str, Any],
|
|
723
|
-
active_context: Mapping[str, Any],
|
|
724
|
-
project_root: Path,
|
|
725
|
-
) -> list[str]:
|
|
726
|
-
"""The user's answers, inlined for a CLI executor.
|
|
727
|
-
|
|
728
|
-
A CLI worker runs in its own process and receives only the prompt text, so
|
|
729
|
-
a path reference never reaches it. Passing the clarification response as a
|
|
730
|
-
path meant the executor implemented the pre-answer plan while the report
|
|
731
|
-
described the answers as carried in — the user answered and nothing
|
|
732
|
-
downstream acted on it.
|
|
733
|
-
"""
|
|
734
|
-
relative = _instruction_path(manifest, active_context, "clarificationResponsePath")
|
|
735
|
-
if not relative:
|
|
736
|
-
return []
|
|
737
|
-
path = _resolve_project_path(project_root, relative)
|
|
738
|
-
if not path.is_file():
|
|
739
|
-
return []
|
|
740
|
-
body = path.read_text(encoding="utf-8").strip()
|
|
741
|
-
if not body:
|
|
742
|
-
return []
|
|
743
|
-
return [
|
|
744
|
-
"# Clarification answers carried in (authoritative)\n\n"
|
|
745
|
-
"The user answered these before this run. Where an answer conflicts "
|
|
746
|
-
"with the approved plan text, the answer wins — implement the answer "
|
|
747
|
-
f"and say so in your result.\n\nSource: `{relative}`\n\n{body}"
|
|
748
|
-
]
|
|
749
|
-
|
|
750
|
-
|
|
751
543
|
def _validate_codex_manifest(manifest: Mapping[str, Any], path: Path) -> None:
|
|
752
544
|
if manifest.get("leadRuntime") != "codex":
|
|
753
545
|
raise DispatchError(f"run manifest is not a Codex lead run: {path}")
|
|
@@ -755,6 +547,24 @@ def _validate_codex_manifest(manifest: Mapping[str, Any], path: Path) -> None:
|
|
|
755
547
|
raise DispatchError(f"run manifest has no leadEventsPath: {path}")
|
|
756
548
|
|
|
757
549
|
|
|
550
|
+
def _requested_worker_model(
|
|
551
|
+
worker_id: str,
|
|
552
|
+
team_state: Mapping[str, Any],
|
|
553
|
+
report_writer_codex_model: str,
|
|
554
|
+
) -> str:
|
|
555
|
+
if worker_id != REPORT_WRITER_WORKER_ID:
|
|
556
|
+
return _require_string(
|
|
557
|
+
_worker_state(team_state, worker_id),
|
|
558
|
+
"modelExecutionValue",
|
|
559
|
+
)
|
|
560
|
+
model = report_writer_codex_model.strip()
|
|
561
|
+
if model:
|
|
562
|
+
return model
|
|
563
|
+
raise DispatchError(
|
|
564
|
+
"Codex report-writer dispatch requires --report-writer-codex-model"
|
|
565
|
+
)
|
|
566
|
+
|
|
567
|
+
|
|
758
568
|
def _select_workers(
|
|
759
569
|
manifest: Mapping[str, Any],
|
|
760
570
|
requested_workers: Sequence[str],
|
|
@@ -1031,23 +841,6 @@ def _failure_reason(exit_code: int, missing_paths: Sequence[Path]) -> str:
|
|
|
1031
841
|
return "worker dispatch failed"
|
|
1032
842
|
|
|
1033
843
|
|
|
1034
|
-
def _replace_report_writer_model_line(
|
|
1035
|
-
prompt_path: Path,
|
|
1036
|
-
model_execution_value: str,
|
|
1037
|
-
) -> None:
|
|
1038
|
-
if not prompt_path.is_file():
|
|
1039
|
-
raise DispatchError(f"report-writer prompt not found: {prompt_path}")
|
|
1040
|
-
lines = prompt_path.read_text(encoding="utf-8").splitlines()
|
|
1041
|
-
marker = "**Model:** Report writer worker,"
|
|
1042
|
-
replacement = f"{marker} {model_execution_value}"
|
|
1043
|
-
updated = [
|
|
1044
|
-
replacement if line.startswith(marker) else line
|
|
1045
|
-
for line in lines
|
|
1046
|
-
]
|
|
1047
|
-
if updated != lines:
|
|
1048
|
-
prompt_path.write_text("\n".join(updated) + "\n", encoding="utf-8")
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
844
|
def _parse_workers(raw: str) -> list[str]:
|
|
1052
845
|
return [item.strip() for item in raw.split(",") if item.strip()]
|
|
1053
846
|
|