okstra 0.147.0 → 0.148.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 +21 -7
- package/docs/architecture/storage-model.md +34 -61
- package/docs/architecture.md +51 -49
- package/docs/cli.md +38 -21
- package/docs/for-ai/skills/okstra-run.md +13 -34
- package/docs/performance-improvement-plan-v2.md +2 -2
- package/docs/pr-template-usage.md +1 -1
- package/docs/project-structure-overview.md +10 -8
- package/docs/task-process/README.md +4 -4
- package/docs/task-process/common-flow.md +12 -12
- package/docs/task-process/final-verification.md +2 -2
- package/docs/task-process/implementation.md +1 -1
- package/docs/task-process/release-handoff.md +1 -1
- package/package.json +2 -2
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/workers/antigravity-worker.md +2 -2
- package/runtime/agents/workers/claude-worker.md +1 -1
- package/runtime/agents/workers/codex-worker.md +2 -2
- package/runtime/agents/workers/grok-worker.md +256 -0
- package/runtime/agents/workers/kimi-worker.md +256 -0
- package/runtime/agents/workers/report-writer-worker.md +2 -2
- package/runtime/bin/lib/okstra/cli.sh +13 -1
- package/runtime/bin/lib/okstra/globals.sh +3 -0
- package/runtime/bin/lib/okstra/usage.sh +17 -12
- package/runtime/bin/okstra-grok-exec.sh +5 -0
- package/runtime/bin/okstra-kimi-exec.sh +5 -0
- package/runtime/bin/okstra-provider-exec.py +235 -0
- package/runtime/bin/okstra.sh +3 -0
- package/runtime/prompts/lead/adapters/antigravity.md +48 -0
- package/runtime/prompts/lead/adapters/claude-code.md +13 -11
- package/runtime/prompts/lead/adapters/codex.md +7 -7
- package/runtime/prompts/lead/okstra-lead-contract.md +1 -1
- package/runtime/prompts/lead/report-writer.md +1 -1
- package/runtime/prompts/profiles/_coding-conventions-preflight.md +1 -1
- package/runtime/prompts/profiles/_common-contract.md +4 -4
- package/runtime/prompts/profiles/_implementation-deliverable.md +2 -2
- package/runtime/prompts/profiles/_implementation-diff-review.md +1 -1
- package/runtime/prompts/profiles/_implementation-executor.md +12 -12
- package/runtime/prompts/profiles/_implementation-self-check.md +4 -4
- package/runtime/prompts/profiles/_implementation-verifier.md +3 -3
- package/runtime/prompts/profiles/change-impact-analysis.md +2 -0
- package/runtime/prompts/profiles/error-analysis.md +2 -0
- package/runtime/prompts/profiles/feature-analysis.md +2 -0
- package/runtime/prompts/profiles/final-verification.md +3 -1
- package/runtime/prompts/profiles/forbidden-actions.json +4 -4
- package/runtime/prompts/profiles/implementation-planning.md +3 -1
- package/runtime/prompts/profiles/implementation.md +2 -2
- package/runtime/prompts/profiles/improvement-discovery.md +3 -1
- package/runtime/prompts/profiles/project-analysis.md +2 -0
- package/runtime/prompts/profiles/release-handoff.md +7 -7
- package/runtime/prompts/profiles/requirements-discovery.md +2 -0
- package/runtime/prompts/wizard/prompts.ko.json +9 -1
- package/runtime/python/okstra_ctl/codex_dispatch.py +68 -87
- package/runtime/python/okstra_ctl/dispatch_core.py +4 -22
- package/runtime/python/okstra_ctl/lead_events.py +1 -1
- package/runtime/python/okstra_ctl/lead_runtime.py +13 -2
- package/runtime/python/okstra_ctl/models.py +156 -8
- package/runtime/python/okstra_ctl/path_hints.py +9 -25
- package/runtime/python/okstra_ctl/paths.py +1 -1
- package/runtime/python/okstra_ctl/render.py +172 -74
- package/runtime/python/okstra_ctl/report_html/common.py +38 -2
- package/runtime/python/okstra_ctl/report_html/filters.py +104 -0
- package/runtime/python/okstra_ctl/report_html/render.py +12 -0
- package/runtime/python/okstra_ctl/report_html/view_models/change_impact_analysis.py +2 -1
- package/runtime/python/okstra_ctl/report_html/view_models/error_analysis.py +2 -1
- package/runtime/python/okstra_ctl/report_html/view_models/feature_analysis.py +2 -1
- package/runtime/python/okstra_ctl/report_html/view_models/final_verification.py +2 -1
- package/runtime/python/okstra_ctl/report_html/view_models/implementation.py +2 -1
- package/runtime/python/okstra_ctl/report_html/view_models/implementation_planning.py +2 -1
- package/runtime/python/okstra_ctl/report_html/view_models/improvement_discovery.py +2 -1
- package/runtime/python/okstra_ctl/report_html/view_models/release_handoff.py +2 -1
- package/runtime/python/okstra_ctl/report_html/view_models/requirements_discovery.py +2 -1
- package/runtime/python/okstra_ctl/report_html/visualizations.py +32 -6
- package/runtime/python/okstra_ctl/run.py +264 -45
- package/runtime/python/okstra_ctl/runner_resolution.py +103 -0
- package/runtime/python/okstra_ctl/team.py +2 -7
- package/runtime/python/okstra_ctl/wizard.py +194 -21
- package/runtime/python/okstra_ctl/worker_artifacts.py +46 -0
- package/runtime/python/okstra_ctl/workers.py +3 -1
- package/runtime/python/okstra_ctl/workflow.py +4 -2
- package/runtime/python/okstra_token_usage/__init__.py +1 -0
- package/runtime/python/okstra_token_usage/collect.py +32 -23
- package/runtime/python/okstra_token_usage/pricing.py +35 -3
- package/runtime/schemas/final-report-v2.0.schema.json +2 -2
- package/runtime/skills/okstra-run/SKILL.md +31 -42
- package/runtime/templates/prd/pr-body.template.md +1 -1
- package/runtime/templates/reports/html/assets/base.css +12 -3
- package/runtime/templates/reports/html/base.template.html +23 -8
- package/runtime/templates/reports/html/macros/forms.html +11 -9
- package/runtime/templates/reports/html/macros/layout.html +5 -5
- package/runtime/templates/reports/html/macros/visualizations.html +11 -1
- package/runtime/templates/reports/html/tasks/change-impact-analysis.template.html +11 -11
- package/runtime/templates/reports/html/tasks/error-analysis.template.html +12 -12
- package/runtime/templates/reports/html/tasks/feature-analysis.template.html +9 -9
- package/runtime/templates/reports/html/tasks/final-verification.template.html +7 -7
- package/runtime/templates/reports/html/tasks/implementation-planning.template.html +12 -12
- package/runtime/templates/reports/html/tasks/implementation.template.html +7 -7
- package/runtime/templates/reports/html/tasks/improvement-discovery.template.html +6 -6
- package/runtime/templates/reports/html/tasks/project-analysis.template.html +11 -11
- package/runtime/templates/reports/html/tasks/release-handoff.template.html +5 -5
- package/runtime/templates/reports/html/tasks/requirements-discovery.template.html +8 -8
- package/runtime/templates/reports/report.js +28 -5
- package/runtime/templates/reports/settings.template.json +4 -0
- package/runtime/templates/reports/task-brief.template.md +7 -7
- package/runtime/validators/validate-run.py +11 -6
- package/runtime/validators/validate_session_conformance.py +2 -1
- package/src/cli-registry.mjs +4 -4
- package/src/commands/execute/codex-dispatch.mjs +7 -10
- package/src/commands/execute/render-bundle.mjs +3 -3
- package/src/commands/execute/run.mjs +17 -52
- package/src/commands/execute/wizard.mjs +4 -1
- package/src/commands/lifecycle/doctor.mjs +6 -3
- package/src/commands/lifecycle/install.mjs +31 -8
- package/src/lib/runtime-manifest.mjs +1 -1
- package/src/lib/runtime-resolver.mjs +2 -2
- package/src/lib/worker-agent-render.mjs +50 -0
|
@@ -13,6 +13,7 @@ from okstra_project.dirs import (
|
|
|
13
13
|
)
|
|
14
14
|
|
|
15
15
|
from .paths import runs_dir_of, task_manifest_file, task_timeline_file
|
|
16
|
+
from .worker_artifacts import artifacts_from_context
|
|
16
17
|
|
|
17
18
|
RUN_CONTEXT_KIND = "run-context"
|
|
18
19
|
RUN_CONTEXT_SCHEMA_VERSION = "2.0"
|
|
@@ -162,7 +163,7 @@ def _compact_active_workers(payload: Mapping[str, Any]) -> list[dict[str, Any]]:
|
|
|
162
163
|
def _compact_active_worker(worker: Mapping[str, Any]) -> dict[str, Any]:
|
|
163
164
|
keep = (
|
|
164
165
|
"workerId", "role", "agent", "agentLabel", "model",
|
|
165
|
-
"modelExecutionValue", "attemptRequired",
|
|
166
|
+
"provider", "runner", "modelExecutionValue", "attemptRequired",
|
|
166
167
|
)
|
|
167
168
|
return {key: worker.get(key, "") for key in keep}
|
|
168
169
|
|
|
@@ -242,19 +243,19 @@ def _hydrate_active_worker(
|
|
|
242
243
|
) -> dict[str, Any]:
|
|
243
244
|
worker_id = str(worker.get("workerId", ""))
|
|
244
245
|
hydrated = dict(worker)
|
|
245
|
-
|
|
246
|
-
hydrated["
|
|
246
|
+
paths = artifacts_from_context(ctx, relative=True).get(worker_id, {})
|
|
247
|
+
hydrated["promptPath"] = paths.get("promptPath", "")
|
|
248
|
+
hydrated["resultPath"] = paths.get("resultPath", "")
|
|
247
249
|
return hydrated
|
|
248
250
|
|
|
249
251
|
|
|
250
252
|
def _hydrate_active_error_logs(ctx: Mapping[str, str]) -> dict[str, Any]:
|
|
253
|
+
artifacts = artifacts_from_context(ctx, relative=True)
|
|
251
254
|
return {
|
|
252
255
|
"runErrorsLogPath": ctx.get("RUN_ERRORS_LOG_RELATIVE_PATH", ""),
|
|
253
256
|
"sidecarsByWorkerId": {
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
"antigravity": ctx.get("ANTIGRAVITY_WORKER_ERRORS_SIDECAR_RELATIVE_PATH", ""),
|
|
257
|
-
"report-writer": ctx.get("REPORT_WRITER_WORKER_ERRORS_SIDECAR_RELATIVE_PATH", ""),
|
|
257
|
+
worker_id: paths["errorsSidecarPath"]
|
|
258
|
+
for worker_id, paths in artifacts.items()
|
|
258
259
|
},
|
|
259
260
|
}
|
|
260
261
|
|
|
@@ -268,23 +269,6 @@ def _hydrate_active_source_artifacts(ctx: Mapping[str, str]) -> dict[str, str]:
|
|
|
268
269
|
}
|
|
269
270
|
|
|
270
271
|
|
|
271
|
-
def _worker_path_key(worker_id: str, kind: str) -> str:
|
|
272
|
-
by_worker = {
|
|
273
|
-
"claude": ("CLAUDE_WORKER_PROMPT_RELATIVE_PATH", "CLAUDE_WORKER_RESULT_RELATIVE_PATH"),
|
|
274
|
-
"codex": ("CODEX_WORKER_PROMPT_RELATIVE_PATH", "CODEX_WORKER_RESULT_RELATIVE_PATH"),
|
|
275
|
-
"antigravity": (
|
|
276
|
-
"ANTIGRAVITY_WORKER_PROMPT_RELATIVE_PATH",
|
|
277
|
-
"ANTIGRAVITY_WORKER_RESULT_RELATIVE_PATH",
|
|
278
|
-
),
|
|
279
|
-
"report-writer": (
|
|
280
|
-
"REPORT_WRITER_WORKER_PROMPT_RELATIVE_PATH",
|
|
281
|
-
"REPORT_WRITER_WORKER_RESULT_RELATIVE_PATH",
|
|
282
|
-
),
|
|
283
|
-
}
|
|
284
|
-
index = 0 if kind == "prompt" else 1
|
|
285
|
-
return by_worker.get(worker_id, ("", ""))[index]
|
|
286
|
-
|
|
287
|
-
|
|
288
272
|
def _compact_run_hints(ctx: Mapping[str, Any]) -> dict[str, Any]:
|
|
289
273
|
return {
|
|
290
274
|
"runRoot": ctx.get("RUN_DIR_RELATIVE_PATH", ""),
|
|
@@ -421,7 +405,7 @@ def _run_files(
|
|
|
421
405
|
"run_manifest_file": run_manifests / f"run-manifest{suffixes['manifests']}.json",
|
|
422
406
|
"run_context_file": run_manifests / f"run-context-{task_type_segment}-{sequences['manifests']}.json",
|
|
423
407
|
"run_inputs_file": run_manifests / f"run-inputs-{task_type_segment}-{sequences['manifests']}.json",
|
|
424
|
-
"run_prompt_snapshot": run_prompts / f"
|
|
408
|
+
"run_prompt_snapshot": run_prompts / f"lead-execution-prompt{suffixes['prompts']}.md",
|
|
425
409
|
"claude_worker_prompt": run_prompts / f"claude-worker-prompt{suffixes['prompts']}.md",
|
|
426
410
|
"codex_worker_prompt": run_prompts / f"codex-worker-prompt{suffixes['prompts']}.md",
|
|
427
411
|
"antigravity_worker_prompt": run_prompts / f"antigravity-worker-prompt{suffixes['prompts']}.md",
|
|
@@ -485,7 +485,7 @@ def compute_run_paths(
|
|
|
485
485
|
run_file_suffix = suffixes["reports"]
|
|
486
486
|
|
|
487
487
|
run_manifest_file = run_manifests / f"run-manifest{suffixes['manifests']}.json"
|
|
488
|
-
run_prompt_snapshot = run_prompts / f"
|
|
488
|
+
run_prompt_snapshot = run_prompts / f"lead-execution-prompt{suffixes['prompts']}.md"
|
|
489
489
|
claude_worker_prompt = run_prompts / f"claude-worker-prompt{suffixes['prompts']}.md"
|
|
490
490
|
codex_worker_prompt = run_prompts / f"codex-worker-prompt{suffixes['prompts']}.md"
|
|
491
491
|
antigravity_worker_prompt = run_prompts / f"antigravity-worker-prompt{suffixes['prompts']}.md"
|
|
@@ -32,8 +32,11 @@ from . import fix_cycles
|
|
|
32
32
|
from .analysis_inputs import ANALYSIS_TASK_TYPES
|
|
33
33
|
from .paths import okstra_home
|
|
34
34
|
from .lead_runtime import lead_runtime_info
|
|
35
|
+
from .models import UnknownProviderError, provider_ids, provider_spec
|
|
36
|
+
from .runner_resolution import native_provider_for_host
|
|
35
37
|
from .path_hints import compact_active_run_context, hydrate_run_context
|
|
36
38
|
from .paths import task_timeline_file
|
|
39
|
+
from .worker_artifacts import artifacts_from_context
|
|
37
40
|
from .workflow import DEFAULT_NEXT_PHASE, PHASE_SEQUENCE
|
|
38
41
|
|
|
39
42
|
|
|
@@ -250,51 +253,106 @@ def _resolve_workers(ctx: dict) -> list[str]:
|
|
|
250
253
|
]
|
|
251
254
|
|
|
252
255
|
|
|
253
|
-
def
|
|
256
|
+
def _assignment_runner_for_ctx(ctx: dict, provider: str) -> str:
|
|
257
|
+
native_provider = native_provider_for_host(_lead_runtime(ctx))
|
|
258
|
+
return "native-session" if provider == native_provider else "cli-wrapper"
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def _lead_assignment(ctx: dict) -> dict:
|
|
262
|
+
raw = ctx.get("LEAD_ASSIGNMENT_JSON", "")
|
|
263
|
+
if raw:
|
|
264
|
+
try:
|
|
265
|
+
parsed = json.loads(raw)
|
|
266
|
+
if isinstance(parsed, dict):
|
|
267
|
+
return parsed
|
|
268
|
+
except json.JSONDecodeError:
|
|
269
|
+
pass
|
|
270
|
+
provider = (
|
|
271
|
+
ctx.get("LEAD_PROVIDER", "")
|
|
272
|
+
or native_provider_for_host(_lead_runtime(ctx))
|
|
273
|
+
or "claude"
|
|
274
|
+
)
|
|
254
275
|
return {
|
|
255
|
-
"
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
"model": ctx.get("CLAUDE_WORKER_MODEL", ""),
|
|
261
|
-
"modelExecutionValue": ctx.get("CLAUDE_WORKER_MODEL_EXECUTION_VALUE", ""),
|
|
262
|
-
"resultPath": ctx.get("CLAUDE_WORKER_RESULT_RELATIVE_PATH", ""),
|
|
263
|
-
"promptPath": ctx.get("CLAUDE_WORKER_PROMPT_RELATIVE_PATH", ""),
|
|
264
|
-
},
|
|
265
|
-
"codex": {
|
|
266
|
-
"workerId": "codex",
|
|
267
|
-
"role": "Codex worker",
|
|
268
|
-
"agent": "codex",
|
|
269
|
-
"agentLabel": "Codex",
|
|
270
|
-
"model": ctx.get("CODEX_WORKER_MODEL", ""),
|
|
271
|
-
"modelExecutionValue": ctx.get("CODEX_WORKER_MODEL_EXECUTION_VALUE", ""),
|
|
272
|
-
"resultPath": ctx.get("CODEX_WORKER_RESULT_RELATIVE_PATH", ""),
|
|
273
|
-
"promptPath": ctx.get("CODEX_WORKER_PROMPT_RELATIVE_PATH", ""),
|
|
274
|
-
},
|
|
275
|
-
"antigravity": {
|
|
276
|
-
"workerId": "antigravity",
|
|
277
|
-
"role": "Antigravity worker",
|
|
278
|
-
"agent": "antigravity",
|
|
279
|
-
"agentLabel": "Antigravity",
|
|
280
|
-
"model": ctx.get("ANTIGRAVITY_WORKER_MODEL", ""),
|
|
281
|
-
"modelExecutionValue": ctx.get("ANTIGRAVITY_WORKER_MODEL_EXECUTION_VALUE", ""),
|
|
282
|
-
"resultPath": ctx.get("ANTIGRAVITY_WORKER_RESULT_RELATIVE_PATH", ""),
|
|
283
|
-
"promptPath": ctx.get("ANTIGRAVITY_WORKER_PROMPT_RELATIVE_PATH", ""),
|
|
284
|
-
},
|
|
285
|
-
"report-writer": {
|
|
286
|
-
"workerId": "report-writer",
|
|
287
|
-
"role": "Report writer worker",
|
|
288
|
-
"agent": "claude",
|
|
289
|
-
"agentLabel": "Claude Code",
|
|
290
|
-
"model": ctx.get("REPORT_WRITER_MODEL", ""),
|
|
291
|
-
"modelExecutionValue": ctx.get("REPORT_WRITER_MODEL_EXECUTION_VALUE", ""),
|
|
292
|
-
"resultPath": ctx.get("REPORT_WRITER_WORKER_RESULT_RELATIVE_PATH", ""),
|
|
293
|
-
"promptPath": ctx.get("REPORT_WRITER_WORKER_PROMPT_RELATIVE_PATH", ""),
|
|
294
|
-
},
|
|
276
|
+
"role": "lead",
|
|
277
|
+
"provider": provider,
|
|
278
|
+
"model": ctx.get("LEAD_MODEL", ""),
|
|
279
|
+
"modelExecutionValue": ctx.get("LEAD_MODEL_EXECUTION_VALUE", ""),
|
|
280
|
+
"runner": _assignment_runner_for_ctx(ctx, provider),
|
|
295
281
|
}
|
|
296
282
|
|
|
297
283
|
|
|
284
|
+
def _worker_assignments(ctx: dict) -> list[dict]:
|
|
285
|
+
raw = ctx.get("WORKER_ASSIGNMENTS_JSON", "")
|
|
286
|
+
if raw:
|
|
287
|
+
try:
|
|
288
|
+
parsed = json.loads(raw)
|
|
289
|
+
if isinstance(parsed, list) and all(isinstance(row, dict) for row in parsed):
|
|
290
|
+
return parsed
|
|
291
|
+
except json.JSONDecodeError:
|
|
292
|
+
pass
|
|
293
|
+
catalog = _worker_catalog(ctx, include_assignments=False)
|
|
294
|
+
return [
|
|
295
|
+
{
|
|
296
|
+
"workerId": worker_id,
|
|
297
|
+
"role": "report-writer" if worker_id == "report-writer" else "analyser",
|
|
298
|
+
"provider": catalog[worker_id]["agent"],
|
|
299
|
+
"model": catalog[worker_id]["model"],
|
|
300
|
+
"modelExecutionValue": catalog[worker_id]["modelExecutionValue"],
|
|
301
|
+
"runner": _assignment_runner_for_ctx(ctx, catalog[worker_id]["agent"]),
|
|
302
|
+
}
|
|
303
|
+
for worker_id in _resolve_workers(ctx)
|
|
304
|
+
]
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
def _worker_catalog(ctx: dict, *, include_assignments: bool = True) -> dict:
|
|
308
|
+
assignments = {} if not include_assignments else {
|
|
309
|
+
row.get("workerId"): row for row in _worker_assignments(ctx)
|
|
310
|
+
if row.get("workerId")
|
|
311
|
+
}
|
|
312
|
+
catalog = {}
|
|
313
|
+
for worker_id, paths in artifacts_from_context(ctx, relative=True).items():
|
|
314
|
+
assignment = assignments.get(worker_id, {})
|
|
315
|
+
provider = assignment.get("provider") or _fallback_worker_provider(ctx, worker_id)
|
|
316
|
+
provider_info = provider_spec(provider)
|
|
317
|
+
model, execution = _legacy_worker_model(ctx, worker_id)
|
|
318
|
+
catalog[worker_id] = {
|
|
319
|
+
"workerId": worker_id,
|
|
320
|
+
"role": (
|
|
321
|
+
"Report writer worker" if worker_id == "report-writer"
|
|
322
|
+
else f"{provider_spec(worker_id).display_label} worker"
|
|
323
|
+
),
|
|
324
|
+
"agent": provider,
|
|
325
|
+
"agentLabel": provider_info.display_label,
|
|
326
|
+
"provider": provider,
|
|
327
|
+
"runner": assignment.get(
|
|
328
|
+
"runner", _assignment_runner_for_ctx(ctx, provider),
|
|
329
|
+
),
|
|
330
|
+
"model": assignment.get("model", model),
|
|
331
|
+
"modelExecutionValue": assignment.get("modelExecutionValue", execution),
|
|
332
|
+
**paths,
|
|
333
|
+
}
|
|
334
|
+
return catalog
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
def _fallback_worker_provider(ctx: dict, worker_id: str) -> str:
|
|
338
|
+
if worker_id == "report-writer":
|
|
339
|
+
return ctx.get("REPORT_WRITER_PROVIDER", "") or "claude"
|
|
340
|
+
return worker_id
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
def _legacy_worker_model(ctx: dict, worker_id: str) -> tuple[str, str]:
|
|
344
|
+
token = "REPORT_WRITER" if worker_id == "report-writer" else (
|
|
345
|
+
worker_id.upper().replace("-", "_")
|
|
346
|
+
)
|
|
347
|
+
return (
|
|
348
|
+
ctx.get(f"{token}_WORKER_MODEL", ctx.get(f"{token}_MODEL", "")),
|
|
349
|
+
ctx.get(
|
|
350
|
+
f"{token}_WORKER_MODEL_EXECUTION_VALUE",
|
|
351
|
+
ctx.get(f"{token}_MODEL_EXECUTION_VALUE", ""),
|
|
352
|
+
),
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
|
|
298
356
|
def _active_workers(ctx: dict) -> list[dict]:
|
|
299
357
|
catalog = _worker_catalog(ctx)
|
|
300
358
|
workers = []
|
|
@@ -305,6 +363,8 @@ def _active_workers(ctx: dict) -> list[dict]:
|
|
|
305
363
|
"role": item["role"],
|
|
306
364
|
"agent": item["agent"],
|
|
307
365
|
"agentLabel": item["agentLabel"],
|
|
366
|
+
"provider": item["provider"],
|
|
367
|
+
"runner": item["runner"],
|
|
308
368
|
"model": item["model"],
|
|
309
369
|
"modelExecutionValue": item["modelExecutionValue"],
|
|
310
370
|
"promptPath": item["promptPath"],
|
|
@@ -378,13 +438,12 @@ def _active_instruction_set(ctx: dict) -> dict:
|
|
|
378
438
|
|
|
379
439
|
|
|
380
440
|
def _active_error_logs(ctx: dict) -> dict:
|
|
441
|
+
artifacts = artifacts_from_context(ctx, relative=True)
|
|
381
442
|
return {
|
|
382
443
|
"runErrorsLogPath": ctx.get("RUN_ERRORS_LOG_RELATIVE_PATH", ""),
|
|
383
444
|
"sidecarsByWorkerId": {
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
"antigravity": ctx.get("ANTIGRAVITY_WORKER_ERRORS_SIDECAR_RELATIVE_PATH", ""),
|
|
387
|
-
"report-writer": ctx.get("REPORT_WRITER_WORKER_ERRORS_SIDECAR_RELATIVE_PATH", ""),
|
|
445
|
+
worker_id: paths["errorsSidecarPath"]
|
|
446
|
+
for worker_id, paths in artifacts.items()
|
|
388
447
|
},
|
|
389
448
|
}
|
|
390
449
|
|
|
@@ -496,6 +555,8 @@ def render_team_state(team_state_path: str, ctx: dict) -> None:
|
|
|
496
555
|
"workerId": m["workerId"],
|
|
497
556
|
"role": m["role"],
|
|
498
557
|
"agent": m["agent"],
|
|
558
|
+
"provider": m["provider"],
|
|
559
|
+
"runner": m["runner"],
|
|
499
560
|
"model": m["model"],
|
|
500
561
|
"modelExecutionValue": m["modelExecutionValue"],
|
|
501
562
|
"status": "not-run",
|
|
@@ -508,7 +569,10 @@ def render_team_state(team_state_path: str, ctx: dict) -> None:
|
|
|
508
569
|
"schemaVersion": "1.0",
|
|
509
570
|
"taskKey": ctx.get("TASK_KEY", ""),
|
|
510
571
|
"taskType": ctx.get("TASK_TYPE", ""),
|
|
572
|
+
"hostRuntime": ctx.get("HOST_RUNTIME", "") or _lead_runtime(ctx),
|
|
511
573
|
"leadRuntime": _lead_runtime(ctx),
|
|
574
|
+
"leadAssignment": _lead_assignment(ctx),
|
|
575
|
+
"workerAssignments": _worker_assignments(ctx),
|
|
512
576
|
"leadAdapter": _lead_adapter(ctx),
|
|
513
577
|
"leadEventsPath": ctx.get("LEAD_EVENTS_RELATIVE_PATH", ""),
|
|
514
578
|
"runDirectoryPath": ctx.get("RUN_DIR_RELATIVE_PATH", ""),
|
|
@@ -516,6 +580,8 @@ def render_team_state(team_state_path: str, ctx: dict) -> None:
|
|
|
516
580
|
"lead": {
|
|
517
581
|
"role": _lead_role(ctx),
|
|
518
582
|
"agent": _lead_agent(ctx),
|
|
583
|
+
"provider": _lead_assignment(ctx).get("provider", ""),
|
|
584
|
+
"runner": _lead_assignment(ctx).get("runner", ""),
|
|
519
585
|
"model": ctx.get("LEAD_MODEL", ""),
|
|
520
586
|
"modelExecutionValue": ctx.get("LEAD_MODEL_EXECUTION_VALUE", ""),
|
|
521
587
|
"status": ctx.get("CURRENT_RUN_STATUS", ""),
|
|
@@ -810,6 +876,7 @@ def migrate_legacy_run_artifacts(ctx: dict) -> None:
|
|
|
810
876
|
legacy_targets = [
|
|
811
877
|
("run-manifest-", ".json", Path(ctx["RUN_MANIFESTS_DIR"])),
|
|
812
878
|
("team-state-", ".json", Path(ctx["RUN_STATE_DIR"])),
|
|
879
|
+
("lead-execution-prompt-", ".md", Path(ctx["RUN_PROMPTS_DIR"])),
|
|
813
880
|
("claude-execution-prompt-", ".md", Path(ctx["RUN_PROMPTS_DIR"])),
|
|
814
881
|
("final-report-", ".md", Path(ctx["RUN_REPORTS_DIR"])),
|
|
815
882
|
("final-", ".status", Path(ctx["RUN_STATUS_DIR"])),
|
|
@@ -866,6 +933,8 @@ def _required_worker_roles(ctx: dict, reviewers: list[str]) -> list[dict]:
|
|
|
866
933
|
"workerId": catalog[item]["workerId"],
|
|
867
934
|
"role": catalog[item]["role"],
|
|
868
935
|
"agent": catalog[item]["agent"],
|
|
936
|
+
"provider": catalog[item]["provider"],
|
|
937
|
+
"runner": catalog[item]["runner"],
|
|
869
938
|
"model": catalog[item]["model"],
|
|
870
939
|
"modelExecutionValue": catalog[item]["modelExecutionValue"],
|
|
871
940
|
"resultPath": catalog[item]["resultPath"],
|
|
@@ -1093,7 +1162,10 @@ def render_task_manifest(manifest_path: str, ctx: dict) -> None:
|
|
|
1093
1162
|
"taskIdPathSegment": ctx.get("TASK_ID_SEGMENT", ""),
|
|
1094
1163
|
"projectRoot": ctx.get("PROJECT_ROOT", ""),
|
|
1095
1164
|
"taskType": ctx.get("TASK_TYPE", ""),
|
|
1165
|
+
"hostRuntime": ctx.get("HOST_RUNTIME", "") or _lead_runtime(ctx),
|
|
1096
1166
|
"leadRuntime": _lead_runtime(ctx),
|
|
1167
|
+
"leadAssignment": _lead_assignment(ctx),
|
|
1168
|
+
"workerAssignments": _worker_assignments(ctx),
|
|
1097
1169
|
"leadAdapter": _lead_adapter(ctx),
|
|
1098
1170
|
"workCategory": work_category,
|
|
1099
1171
|
# user-managed status (set-work-status CLI) — carried across re-renders
|
|
@@ -1151,6 +1223,8 @@ def render_task_manifest(manifest_path: str, ctx: dict) -> None:
|
|
|
1151
1223
|
"referenceExpectationsPath": ctx.get(
|
|
1152
1224
|
"REFERENCE_EXPECTATIONS_RELATIVE_PATH", ""
|
|
1153
1225
|
),
|
|
1226
|
+
"leadExecutionPromptPath": ctx.get("INSTRUCTION_SET_RELATIVE_PATH", "")
|
|
1227
|
+
+ "/lead-execution-prompt.md",
|
|
1154
1228
|
"claudeExecutionPromptPath": ctx.get("INSTRUCTION_SET_RELATIVE_PATH", "")
|
|
1155
1229
|
+ "/claude-execution-prompt.md",
|
|
1156
1230
|
"leadPromptSnapshotPath": ctx.get("RUN_PROMPT_SNAPSHOT_RELATIVE_PATH", ""),
|
|
@@ -1174,7 +1248,7 @@ def render_task_manifest(manifest_path: str, ctx: dict) -> None:
|
|
|
1174
1248
|
"leadExecutionMode": "synthesis-only",
|
|
1175
1249
|
"finalSynthesisOwner": lead_role,
|
|
1176
1250
|
"artifactFirst": True,
|
|
1177
|
-
"resultCollectionMode": "
|
|
1251
|
+
"resultCollectionMode": "lead-managed",
|
|
1178
1252
|
"finalReportFormat": "markdown",
|
|
1179
1253
|
"finalReportFilename": ctx.get("FINAL_REPORT_FILENAME", ""),
|
|
1180
1254
|
"finalStatusFilename": ctx.get("FINAL_STATUS_FILENAME", ""),
|
|
@@ -1182,7 +1256,7 @@ def render_task_manifest(manifest_path: str, ctx: dict) -> None:
|
|
|
1182
1256
|
"requiredWorkerAttempts": reviewers,
|
|
1183
1257
|
"requiredWorkerRoles": required_worker_roles,
|
|
1184
1258
|
"requiredAgentStatusEntries": required_agent_status_entries,
|
|
1185
|
-
"
|
|
1259
|
+
"requireDistinctLeadFromWorkerSession": True,
|
|
1186
1260
|
"requireAllRequiredWorkerAttempts": True,
|
|
1187
1261
|
"requireAntigravityWorkerAttempt": "antigravity" in reviewers,
|
|
1188
1262
|
"requireCollectedWorkerStatusesBeforeFinalVerdict": True,
|
|
@@ -1227,14 +1301,20 @@ def render_task_manifest(manifest_path: str, ctx: dict) -> None:
|
|
|
1227
1301
|
"teamStatePath": ctx.get("TEAM_STATE_RELATIVE_PATH", ""),
|
|
1228
1302
|
"validatorScriptPath": ctx.get("RUN_VALIDATOR_RELATIVE_PATH", ""),
|
|
1229
1303
|
},
|
|
1230
|
-
"
|
|
1304
|
+
"leadSession": {
|
|
1231
1305
|
"sessionId": ctx.get("CLAUDE_SESSION_ID", ""),
|
|
1232
1306
|
"resumeCommandPath": ctx.get("CLAUDE_RESUME_COMMAND_RELATIVE_PATH", ""),
|
|
1307
|
+
"accounting": _lead_info(ctx).session_accounting,
|
|
1233
1308
|
},
|
|
1234
1309
|
"convergence": convergence_block,
|
|
1235
1310
|
"createdAt": existing.get("createdAt") or ctx.get("RUN_TIMESTAMP_ISO", ""),
|
|
1236
1311
|
"updatedAt": ctx.get("RUN_TIMESTAMP_ISO", ""),
|
|
1237
1312
|
}
|
|
1313
|
+
if _lead_runtime(ctx) == "claude-code":
|
|
1314
|
+
payload["claudeSession"] = {
|
|
1315
|
+
"sessionId": ctx.get("CLAUDE_SESSION_ID", ""),
|
|
1316
|
+
"resumeCommandPath": ctx.get("CLAUDE_RESUME_COMMAND_RELATIVE_PATH", ""),
|
|
1317
|
+
}
|
|
1238
1318
|
_write_json(path, payload)
|
|
1239
1319
|
|
|
1240
1320
|
|
|
@@ -1255,7 +1335,7 @@ def _build_convergence_block(ctx: dict) -> dict:
|
|
|
1255
1335
|
ctx knobs honoured:
|
|
1256
1336
|
- `OKSTRA_PLAN_VERIFICATION`: "true" | "false" | "" (empty → default True).
|
|
1257
1337
|
Wired from CLI `--no-plan-verification` (sets "false").
|
|
1258
|
-
- `CRITIC_CHOICE`: "" | "off" |
|
|
1338
|
+
- `CRITIC_CHOICE`: "" | "off" | a provider with the critic capability — critic
|
|
1259
1339
|
backing provider (enabled only for requirements-discovery / error-analysis /
|
|
1260
1340
|
implementation-planning / final-verification); model taken from that
|
|
1261
1341
|
provider's execution value.
|
|
@@ -1277,16 +1357,24 @@ def _build_convergence_block(ctx: dict) -> dict:
|
|
|
1277
1357
|
# Independent of `adversarial_phases` above (they answer different questions and
|
|
1278
1358
|
# may diverge): the coverage critic is opt-in for the finding-producing phases.
|
|
1279
1359
|
critic_phases = {"requirements-discovery", "error-analysis", "implementation-planning", "final-verification"}
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1360
|
+
critic_enabled = critic_choice in provider_ids("critic") and task_type in critic_phases
|
|
1361
|
+
assignment = next(
|
|
1362
|
+
(
|
|
1363
|
+
row for row in _worker_assignments(ctx)
|
|
1364
|
+
if row.get("provider") == critic_choice and row.get("role") != "report-writer"
|
|
1365
|
+
),
|
|
1366
|
+
{},
|
|
1367
|
+
)
|
|
1368
|
+
_, legacy_execution = _legacy_worker_model(ctx, critic_choice)
|
|
1286
1369
|
critic_block = {
|
|
1287
1370
|
"enabled": critic_enabled,
|
|
1288
1371
|
"provider": critic_choice if critic_enabled else None,
|
|
1289
|
-
"modelExecutionValue": (
|
|
1372
|
+
"modelExecutionValue": (
|
|
1373
|
+
ctx.get("CRITIC_MODEL_EXECUTION_VALUE")
|
|
1374
|
+
or assignment.get("modelExecutionValue")
|
|
1375
|
+
or legacy_execution
|
|
1376
|
+
or None
|
|
1377
|
+
) if critic_enabled else None,
|
|
1290
1378
|
}
|
|
1291
1379
|
return {
|
|
1292
1380
|
"enabled": True,
|
|
@@ -1353,9 +1441,12 @@ def render_run_manifest(run_manifest_path: str, ctx: dict) -> None:
|
|
|
1353
1441
|
"taskId": ctx.get("TASK_ID", ""),
|
|
1354
1442
|
"taskKey": ctx.get("TASK_KEY", ""),
|
|
1355
1443
|
"taskType": ctx.get("TASK_TYPE", ""),
|
|
1444
|
+
"hostRuntime": ctx.get("HOST_RUNTIME", "") or _lead_runtime(ctx),
|
|
1356
1445
|
"leadRuntime": _lead_runtime(ctx),
|
|
1357
1446
|
"leadRuntimeRequest": ctx.get("LEAD_RUNTIME_REQUEST", "") or _lead_runtime(ctx),
|
|
1358
1447
|
"runtimeResolution": _runtime_resolution(ctx),
|
|
1448
|
+
"leadAssignment": _lead_assignment(ctx),
|
|
1449
|
+
"workerAssignments": _worker_assignments(ctx),
|
|
1359
1450
|
"leadAdapter": _lead_adapter(ctx),
|
|
1360
1451
|
"workCategory": task_manifest.get(
|
|
1361
1452
|
"workCategory", ctx.get("WORKFLOW_WORK_CATEGORY", "unknown")
|
|
@@ -1405,7 +1496,7 @@ def render_run_manifest(run_manifest_path: str, ctx: dict) -> None:
|
|
|
1405
1496
|
"workerResultsDirectoryPath": ctx.get("WORKER_RESULTS_RELATIVE_PATH", ""),
|
|
1406
1497
|
"reportTemplatePath": ctx.get("FINAL_REPORT_TEMPLATE_RELATIVE_PATH", ""),
|
|
1407
1498
|
"validatorScriptPath": ctx.get("RUN_VALIDATOR_RELATIVE_PATH", ""),
|
|
1408
|
-
"
|
|
1499
|
+
"leadSessionId": ctx.get("CLAUDE_SESSION_ID", ""),
|
|
1409
1500
|
"resumeCommandPath": ctx.get("CLAUDE_RESUME_COMMAND_RELATIVE_PATH", ""),
|
|
1410
1501
|
"concurrentRun": {
|
|
1411
1502
|
"detected": bool(concurrent_run_stages),
|
|
@@ -1446,7 +1537,7 @@ def render_run_manifest(run_manifest_path: str, ctx: dict) -> None:
|
|
|
1446
1537
|
"requiredWorkerRoles": required_worker_roles,
|
|
1447
1538
|
"requiredAgentStatusEntries": [lead_role]
|
|
1448
1539
|
+ [catalog[item]["role"] for item in reviewers],
|
|
1449
|
-
"
|
|
1540
|
+
"requireDistinctLeadFromWorkerSession": True,
|
|
1450
1541
|
"requireAllRequiredWorkerAttempts": True,
|
|
1451
1542
|
"requireAntigravityWorkerAttempt": "antigravity" in reviewers,
|
|
1452
1543
|
"requireCollectedWorkerStatusesBeforeFinalVerdict": True,
|
|
@@ -1485,6 +1576,8 @@ def render_run_manifest(run_manifest_path: str, ctx: dict) -> None:
|
|
|
1485
1576
|
if ctx.get("TASK_TYPE") == "implementation-planning"
|
|
1486
1577
|
else []
|
|
1487
1578
|
)
|
|
1579
|
+
if _lead_runtime(ctx) == "claude-code":
|
|
1580
|
+
payload["claudeSessionId"] = ctx.get("CLAUDE_SESSION_ID", "")
|
|
1488
1581
|
_write_json(Path(run_manifest_path), payload)
|
|
1489
1582
|
|
|
1490
1583
|
|
|
@@ -1497,12 +1590,7 @@ def render_timeline(timeline_path: str, ctx: dict) -> None:
|
|
|
1497
1590
|
except Exception:
|
|
1498
1591
|
task_manifest = {}
|
|
1499
1592
|
reviewers = _resolve_workers(ctx)
|
|
1500
|
-
|
|
1501
|
-
"claude": ctx.get("CLAUDE_WORKER_PROMPT_RELATIVE_PATH", ""),
|
|
1502
|
-
"codex": ctx.get("CODEX_WORKER_PROMPT_RELATIVE_PATH", ""),
|
|
1503
|
-
"antigravity": ctx.get("ANTIGRAVITY_WORKER_PROMPT_RELATIVE_PATH", ""),
|
|
1504
|
-
"report-writer": ctx.get("REPORT_WRITER_WORKER_PROMPT_RELATIVE_PATH", ""),
|
|
1505
|
-
}
|
|
1593
|
+
catalog = _worker_catalog(ctx)
|
|
1506
1594
|
path = Path(timeline_path)
|
|
1507
1595
|
existing = {}
|
|
1508
1596
|
if path.exists():
|
|
@@ -1548,7 +1636,7 @@ def render_timeline(timeline_path: str, ctx: dict) -> None:
|
|
|
1548
1636
|
"promptSnapshotPath": ctx.get("RUN_PROMPT_SNAPSHOT_RELATIVE_PATH", ""),
|
|
1549
1637
|
"workerPromptDirectoryPath": ctx.get("RUN_PROMPTS_RELATIVE_PATH", ""),
|
|
1550
1638
|
"workerPromptPathByWorkerId": {
|
|
1551
|
-
item:
|
|
1639
|
+
item: catalog[item]["promptPath"] for item in reviewers
|
|
1552
1640
|
},
|
|
1553
1641
|
"reportPath": ctx.get("LATEST_REPORT_RELATIVE_PATH")
|
|
1554
1642
|
or ctx.get("FINAL_REPORT_RELATIVE_PATH", ""),
|
|
@@ -1654,6 +1742,24 @@ def render_task_index(template_path: str, output_path: str, ctx: dict) -> None:
|
|
|
1654
1742
|
or ctx.get("CLAUDE_RESUME_COMMAND_RELATIVE_PATH", "")
|
|
1655
1743
|
or "--"
|
|
1656
1744
|
)
|
|
1745
|
+
model_assignment_lines = [
|
|
1746
|
+
f"- `{lead_role}`: `{rc.get('leadModel', ctx.get('LEAD_MODEL', ''))}`"
|
|
1747
|
+
]
|
|
1748
|
+
for assignment in task_manifest.get("workerAssignments", []):
|
|
1749
|
+
if not isinstance(assignment, dict):
|
|
1750
|
+
continue
|
|
1751
|
+
worker_id = assignment.get("workerId", "")
|
|
1752
|
+
provider = assignment.get("provider", "")
|
|
1753
|
+
if worker_id == "report-writer":
|
|
1754
|
+
label = "Report writer worker"
|
|
1755
|
+
else:
|
|
1756
|
+
try:
|
|
1757
|
+
label = f"{provider_spec(provider).display_label} worker"
|
|
1758
|
+
except (UnknownProviderError, TypeError):
|
|
1759
|
+
label = f"{worker_id} worker"
|
|
1760
|
+
model_assignment_lines.append(
|
|
1761
|
+
f"- `{label}`: `{assignment.get('model', '')}`"
|
|
1762
|
+
)
|
|
1657
1763
|
mapping = {
|
|
1658
1764
|
"{{TASK_KEY}}": task_manifest.get("taskKey", ctx.get("TASK_KEY", "")),
|
|
1659
1765
|
"{{TASK_TYPE}}": task_manifest.get("taskType", ctx.get("TASK_TYPE", "")),
|
|
@@ -1686,15 +1792,7 @@ def render_task_index(template_path: str, output_path: str, ctx: dict) -> None:
|
|
|
1686
1792
|
"status", ctx.get("VALIDATION_STATUS", "not-run")
|
|
1687
1793
|
),
|
|
1688
1794
|
"{{CLAUDE_RESUME_COMMAND_RELATIVE_PATH}}": latest_resume_command,
|
|
1689
|
-
"{{MODEL_ASSIGNMENT_LINES}}": "\n".join(
|
|
1690
|
-
[
|
|
1691
|
-
f"- `{lead_role}`: `{rc.get('leadModel', ctx.get('LEAD_MODEL', ''))}`",
|
|
1692
|
-
f"- `Claude worker`: `{ctx.get('CLAUDE_WORKER_MODEL', '')}`",
|
|
1693
|
-
f"- `Codex worker`: `{ctx.get('CODEX_WORKER_MODEL', '')}`",
|
|
1694
|
-
f"- `Antigravity worker`: `{ctx.get('ANTIGRAVITY_WORKER_MODEL', '')}`",
|
|
1695
|
-
f"- `Report writer worker`: `{ctx.get('REPORT_WRITER_MODEL', '')}`",
|
|
1696
|
-
]
|
|
1697
|
-
),
|
|
1795
|
+
"{{MODEL_ASSIGNMENT_LINES}}": "\n".join(model_assignment_lines),
|
|
1698
1796
|
"{{TASK_MANIFEST_RELATIVE_PATH}}": task_manifest.get(
|
|
1699
1797
|
"taskManifestPath", ctx.get("TASK_MANIFEST_RELATIVE_PATH", "")
|
|
1700
1798
|
),
|
|
@@ -10,19 +10,55 @@ def audit_context(data: dict) -> dict[str, object]:
|
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
|
|
13
|
+
_LEDGER_TEXT_KEYS = ("statement", "evidence", "hypothesis", "question")
|
|
14
|
+
_LEDGER_SOURCE_KEYS = ("source", "confidence", "reason")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _first_filled(row: dict, keys: tuple[str, ...]) -> str:
|
|
18
|
+
for key in keys:
|
|
19
|
+
value = row.get(key)
|
|
20
|
+
if value:
|
|
21
|
+
return str(value)
|
|
22
|
+
return ""
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _ledger_row(row: dict) -> dict[str, object]:
|
|
26
|
+
"""Flatten one evidence row into the single shape the ledger renders.
|
|
27
|
+
|
|
28
|
+
Every row type that reaches the ledger is ``additionalProperties: false``
|
|
29
|
+
and names its content and source differently, so no row carries another
|
|
30
|
+
row type's keys:
|
|
31
|
+
|
|
32
|
+
- ``evidence.primary`` — ``evidence`` / ``source``
|
|
33
|
+
- ``evidence.secondary`` — ``hypothesis`` / ``confidence``
|
|
34
|
+
- ``analysisCommon.confirmedFacts`` — ``statement`` / ``currentCodeEvidence``
|
|
35
|
+
- ``analysisCommon.inferences`` — ``statement`` / ``confidence``
|
|
36
|
+
- ``analysisCommon.unknowns`` — ``question`` / ``reason``
|
|
37
|
+
|
|
38
|
+
Only this function knows which block a row came from, so the key hunt
|
|
39
|
+
belongs here rather than in the template.
|
|
40
|
+
"""
|
|
41
|
+
return {
|
|
42
|
+
"id": row.get("id", ""),
|
|
43
|
+
"text": _first_filled(row, _LEDGER_TEXT_KEYS),
|
|
44
|
+
"codeEvidence": row.get("currentCodeEvidence") or [],
|
|
45
|
+
"source": _first_filled(row, _LEDGER_SOURCE_KEYS),
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
|
|
13
49
|
def evidence_index(data: dict) -> dict[str, object]:
|
|
14
50
|
rows: dict[str, object] = {}
|
|
15
51
|
for section in ("primary", "secondary"):
|
|
16
52
|
for row in data.get("evidence", {}).get(section, []):
|
|
17
53
|
row_id = row.get("id")
|
|
18
54
|
if row_id:
|
|
19
|
-
rows[row_id] = row
|
|
55
|
+
rows[row_id] = _ledger_row(row)
|
|
20
56
|
analysis = data.get("analysisCommon", {})
|
|
21
57
|
for collection in ("confirmedFacts", "inferences", "unknowns"):
|
|
22
58
|
for row in analysis.get(collection, []):
|
|
23
59
|
row_id = row.get("id")
|
|
24
60
|
if row_id:
|
|
25
|
-
rows[row_id] = row
|
|
61
|
+
rows[row_id] = _ledger_row(row)
|
|
26
62
|
return rows
|
|
27
63
|
|
|
28
64
|
|