okstra 0.179.2 → 0.183.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/cli-registry.mjs +30 -2
- package/dist/cli-registry.mjs.map +1 -1
- package/dist/commands/execute/incremental-carry.mjs +9 -8
- package/dist/commands/execute/incremental-carry.mjs.map +1 -1
- package/dist/commands/execute/plan-verify.mjs +3 -1
- package/dist/commands/execute/plan-verify.mjs.map +1 -1
- package/dist/commands/execute/render-bundle.d.mts +4 -2
- package/dist/commands/execute/render-bundle.mjs +46 -5
- package/dist/commands/execute/render-bundle.mjs.map +1 -1
- package/dist/commands/execute/run.mjs +11 -3
- package/dist/commands/execute/run.mjs.map +1 -1
- package/dist/commands/inspect/model-io.d.mts +1 -0
- package/dist/commands/inspect/model-io.mjs +25 -0
- package/dist/commands/inspect/model-io.mjs.map +1 -0
- package/dist/commands/inspect/stage-map.mjs +29 -8
- package/dist/commands/inspect/stage-map.mjs.map +1 -1
- package/dist/commands/inspect/task-list.mjs +52 -6
- package/dist/commands/inspect/task-list.mjs.map +1 -1
- package/dist/commands/inspect/user-response.mjs +14 -4
- package/dist/commands/inspect/user-response.mjs.map +1 -1
- package/dist/commands/lifecycle/check-project.d.mts +1 -0
- package/dist/commands/lifecycle/check-project.mjs +69 -50
- package/dist/commands/lifecycle/check-project.mjs.map +1 -1
- package/dist/commands/lifecycle/contract-check.d.mts +1 -0
- package/dist/commands/lifecycle/contract-check.mjs +18 -0
- package/dist/commands/lifecycle/contract-check.mjs.map +1 -0
- package/dist/commands/lifecycle/preflight.mjs +154 -51
- package/dist/commands/lifecycle/preflight.mjs.map +1 -1
- package/dist/commands/pr/pr.d.mts +1 -0
- package/dist/commands/pr/pr.mjs +19 -1
- package/dist/commands/pr/pr.mjs.map +1 -1
- package/dist/commands/report/agent-activity.mjs +2 -2
- package/dist/commands/report/approval-decision.d.mts +1 -0
- package/dist/commands/report/approval-decision.mjs +21 -0
- package/dist/commands/report/approval-decision.mjs.map +1 -0
- package/dist/commands/report/design-snapshot.d.mts +1 -0
- package/dist/commands/report/design-snapshot.mjs +19 -0
- package/dist/commands/report/design-snapshot.mjs.map +1 -0
- package/dist/commands/report/translate.mjs +3 -0
- package/dist/commands/report/translate.mjs.map +1 -1
- package/dist/lib/host-registry-client.mjs +13 -9
- package/dist/lib/host-registry-client.mjs.map +1 -1
- package/docs/architecture/storage-model.md +1 -1
- package/docs/architecture.md +21 -10
- package/docs/cli.md +36 -20
- package/docs/container.md +6 -4
- package/docs/contributor-change-matrix.md +1 -1
- package/docs/for-ai/README.md +2 -2
- package/docs/for-ai/skills/okstra-brief-gen.md +5 -3
- package/docs/for-ai/skills/okstra-code-review.md +4 -4
- package/docs/for-ai/skills/okstra-container-build.md +20 -17
- package/docs/for-ai/skills/okstra-inspect.md +20 -23
- package/docs/for-ai/skills/okstra-manager.md +19 -18
- package/docs/for-ai/skills/okstra-memory.md +2 -2
- package/docs/for-ai/skills/okstra-pr-gen.md +3 -3
- package/docs/for-ai/skills/okstra-rollup.md +14 -13
- package/docs/for-ai/skills/okstra-run.md +7 -3
- package/docs/for-ai/skills/okstra-schedule-gen.md +15 -18
- package/docs/for-ai/skills/okstra-setup.md +7 -7
- package/docs/for-ai/skills/okstra-usage.md +5 -4
- package/docs/for-ai/skills/okstra-user-response.md +50 -32
- package/docs/project-structure-overview.md +43 -31
- package/docs/task-process/implementation-planning.md +2 -2
- package/docs/task-process/implementation.md +7 -4
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/workers/claude-worker.md +4 -4
- package/runtime/agents/workers/report-writer-worker.md +15 -164
- package/runtime/agents/workers/translator-worker.md +5 -13
- package/runtime/bin/okstra-error-log.py +51 -11
- package/runtime/bin/okstra-report-translate.py +210 -23
- package/runtime/prompts/host-orchestration/implementation.md +1 -1
- package/runtime/prompts/launch.template.md +9 -12
- package/runtime/prompts/lead/adapters/cmux.md +1 -1
- package/runtime/prompts/lead/context-loader.md +41 -141
- package/runtime/prompts/lead/convergence.md +10 -8
- package/runtime/prompts/lead/okstra-lead-contract.md +40 -49
- package/runtime/prompts/lead/plan-body-verification.md +217 -23
- package/runtime/prompts/lead/report-writer.md +81 -421
- package/runtime/prompts/lead/team-contract.md +9 -54
- package/runtime/prompts/profiles/_clarification-recommendation.md +5 -4
- package/runtime/prompts/profiles/_coding-conventions-preflight.md +3 -2
- package/runtime/prompts/profiles/_common-contract.md +4 -4
- package/runtime/prompts/profiles/_implementation-deliverable.md +1 -1
- package/runtime/prompts/profiles/_implementation-diff-review.md +1 -1
- package/runtime/prompts/profiles/_implementation-verifier.md +3 -3
- package/runtime/prompts/profiles/change-impact-analysis.md +1 -1
- package/runtime/prompts/profiles/error-analysis.md +1 -1
- package/runtime/prompts/profiles/feature-analysis.md +1 -1
- package/runtime/prompts/profiles/final-verification.md +1 -1
- package/runtime/prompts/profiles/implementation-planning.md +20 -12
- package/runtime/prompts/profiles/improvement-discovery.md +1 -1
- package/runtime/prompts/profiles/project-analysis.md +1 -1
- package/runtime/prompts/profiles/release-handoff.md +0 -1
- package/runtime/prompts/profiles/requirements-discovery.md +1 -1
- package/runtime/prompts/wizard/prompts.ko.json +0 -1
- package/runtime/python/okstra_ctl/adapters/hosts/capability_adapter.py +69 -17
- package/runtime/python/okstra_ctl/adapters/hosts/claude-code/adapter.py +13 -4
- package/runtime/python/okstra_ctl/adapters/hosts/claude-code/relay.md +8 -2
- package/runtime/python/okstra_ctl/adapters/hosts/codex/adapter.py +2 -2
- package/runtime/python/okstra_ctl/adapters/hosts/codex/relay.md +50 -5
- package/runtime/python/okstra_ctl/adapters/hosts/external/relay.md +1 -1
- package/runtime/python/okstra_ctl/adapters/hosts/grok/adapter.py +2 -2
- package/runtime/python/okstra_ctl/adapters/hosts/grok/relay.md +66 -5
- package/runtime/python/okstra_ctl/adapters/providers/grok/adapter.py +70 -2
- package/runtime/python/okstra_ctl/agent_activity.py +141 -38
- package/runtime/python/okstra_ctl/agent_invocation.py +19 -6
- package/runtime/python/okstra_ctl/agent_prompt_cli.py +70 -23
- package/runtime/python/okstra_ctl/analysis_inputs.py +5 -4
- package/runtime/python/okstra_ctl/analysis_packet.py +124 -3
- package/runtime/python/okstra_ctl/approval_decisions.py +328 -0
- package/runtime/python/okstra_ctl/attempt_evidence.py +2 -2
- package/runtime/python/okstra_ctl/backfill.py +13 -10
- package/runtime/python/okstra_ctl/batch.py +2 -4
- package/runtime/python/okstra_ctl/build_tools.py +6 -3
- package/runtime/python/okstra_ctl/claim_reproduction.py +101 -0
- package/runtime/python/okstra_ctl/clarification_items.py +27 -13
- package/runtime/python/okstra_ctl/cmux.py +130 -52
- package/runtime/python/okstra_ctl/code_review_target.py +34 -8
- package/runtime/python/okstra_ctl/consumers.py +5 -4
- package/runtime/python/okstra_ctl/container.py +103 -8
- package/runtime/python/okstra_ctl/context_cost.py +2 -1
- package/runtime/python/okstra_ctl/contract_graph.py +497 -0
- package/runtime/python/okstra_ctl/contract_graph_cli.py +62 -0
- package/runtime/python/okstra_ctl/convergence.py +338 -17
- package/runtime/python/okstra_ctl/convergence_engine.py +10 -18
- package/runtime/python/okstra_ctl/convergence_provenance.py +58 -8
- package/runtime/python/okstra_ctl/convergence_store.py +55 -34
- package/runtime/python/okstra_ctl/design_prep.py +7 -4
- package/runtime/python/okstra_ctl/design_snapshot.py +134 -0
- package/runtime/python/okstra_ctl/dispatch_core.py +97 -69
- package/runtime/python/okstra_ctl/dispatch_state.py +163 -63
- package/runtime/python/okstra_ctl/doctor.py +6 -3
- package/runtime/python/okstra_ctl/domain/worker_presentation.py +70 -9
- package/runtime/python/okstra_ctl/entrypoints/hosts.py +16 -30
- package/runtime/python/okstra_ctl/error_log_write.py +35 -30
- package/runtime/python/okstra_ctl/error_report.py +26 -1
- package/runtime/python/okstra_ctl/error_zip.py +27 -5
- package/runtime/python/okstra_ctl/execution_identity.py +3 -2
- package/runtime/python/okstra_ctl/execution_manifest.py +7 -4
- package/runtime/python/okstra_ctl/execution_mutation_audit.py +6 -2
- package/runtime/python/okstra_ctl/final_report_schema.py +26 -17
- package/runtime/python/okstra_ctl/fix_cycles.py +2 -2
- package/runtime/python/okstra_ctl/fixed_text.py +39 -0
- package/runtime/python/okstra_ctl/git_reconcile.py +41 -9
- package/runtime/python/okstra_ctl/handoff.py +5 -4
- package/runtime/python/okstra_ctl/i18n.py +4 -2
- package/runtime/python/okstra_ctl/implementation_direction.py +22 -14
- package/runtime/python/okstra_ctl/implementation_outcome.py +4 -7
- package/runtime/python/okstra_ctl/incremental_carry.py +130 -17
- package/runtime/python/okstra_ctl/incremental_scope.py +12 -4
- package/runtime/python/okstra_ctl/index.py +8 -11
- package/runtime/python/okstra_ctl/initial_prompt_materialization.py +79 -7
- package/runtime/python/okstra_ctl/invocation.py +3 -6
- package/runtime/python/okstra_ctl/json_boundary.py +366 -0
- package/runtime/python/okstra_ctl/json_registry.py +10 -12
- package/runtime/python/okstra_ctl/jsonl.py +19 -2
- package/runtime/python/okstra_ctl/lead_events.py +33 -1
- package/runtime/python/okstra_ctl/listing.py +3 -3
- package/runtime/python/okstra_ctl/log_report.py +24 -2
- package/runtime/python/okstra_ctl/manager_cli.py +92 -7
- package/runtime/python/okstra_ctl/manager_store.py +12 -10
- package/runtime/python/okstra_ctl/material.py +5 -1
- package/runtime/python/okstra_ctl/migrate.py +29 -25
- package/runtime/python/okstra_ctl/model_cli.py +3 -15
- package/runtime/python/okstra_ctl/model_io_cli.py +1051 -0
- package/runtime/python/okstra_ctl/mutation_probe.py +13 -4
- package/runtime/python/okstra_ctl/pane_reclaim.py +3 -2
- package/runtime/python/okstra_ctl/path_hints.py +12 -0
- package/runtime/python/okstra_ctl/paths.py +21 -0
- package/runtime/python/okstra_ctl/plan_items.py +115 -4
- package/runtime/python/okstra_ctl/plan_items_cli.py +626 -34
- package/runtime/python/okstra_ctl/ports/worker_dispatch.py +2 -1
- package/runtime/python/okstra_ctl/pr_template.py +3 -2
- package/runtime/python/okstra_ctl/project_meta.py +5 -7
- package/runtime/python/okstra_ctl/recap.py +5 -4
- package/runtime/python/okstra_ctl/reconcile.py +21 -27
- package/runtime/python/okstra_ctl/registry/host_discovery.py +3 -2
- package/runtime/python/okstra_ctl/registry/provider_registry.py +3 -2
- package/runtime/python/okstra_ctl/render.py +74 -16
- package/runtime/python/okstra_ctl/render_final_report.py +10 -8
- package/runtime/python/okstra_ctl/report_assembly.py +509 -0
- package/runtime/python/okstra_ctl/report_contract.py +2 -1
- package/runtime/python/okstra_ctl/report_finalize.py +67 -32
- package/runtime/python/okstra_ctl/report_html/render.py +3 -2
- package/runtime/python/okstra_ctl/report_inputs.py +72 -0
- package/runtime/python/okstra_ctl/report_language.py +3 -2
- package/runtime/python/okstra_ctl/report_markdown.py +81 -8
- package/runtime/python/okstra_ctl/report_narrative.py +351 -0
- package/runtime/python/okstra_ctl/report_projections.py +265 -0
- package/runtime/python/okstra_ctl/report_synthesis_packet.py +518 -0
- package/runtime/python/okstra_ctl/report_views.py +3 -2
- package/runtime/python/okstra_ctl/rollup.py +65 -4
- package/runtime/python/okstra_ctl/run.py +184 -65
- package/runtime/python/okstra_ctl/run_audit.py +3 -2
- package/runtime/python/okstra_ctl/run_context.py +6 -9
- package/runtime/python/okstra_ctl/run_index_row.py +2 -8
- package/runtime/python/okstra_ctl/schedule_semantics.py +5 -2
- package/runtime/python/okstra_ctl/schema_excerpt.py +15 -8
- package/runtime/python/okstra_ctl/session_transcript.py +27 -1
- package/runtime/python/okstra_ctl/set_work_status.py +64 -38
- package/runtime/python/okstra_ctl/stage_fix_carry.py +8 -6
- package/runtime/python/okstra_ctl/stage_ledger.py +132 -18
- package/runtime/python/okstra_ctl/stage_map.py +96 -28
- package/runtime/python/okstra_ctl/stage_targets.py +3 -4
- package/runtime/python/okstra_ctl/team.py +3 -2
- package/runtime/python/okstra_ctl/team_reconcile.py +11 -2
- package/runtime/python/okstra_ctl/time_report.py +51 -4
- package/runtime/python/okstra_ctl/usage_identity.py +2 -1
- package/runtime/python/okstra_ctl/usage_report.py +58 -4
- package/runtime/python/okstra_ctl/user_response.py +1431 -66
- package/runtime/python/okstra_ctl/wizard.py +11 -44
- package/runtime/python/okstra_ctl/work_categories.py +3 -2
- package/runtime/python/okstra_ctl/worker_dispatch.py +5 -2
- package/runtime/python/okstra_ctl/worker_prompt_body.py +49 -3
- package/runtime/python/okstra_ctl/worker_prompt_contract.py +3 -2
- package/runtime/python/okstra_ctl/worker_prompt_policy.py +31 -3
- package/runtime/python/okstra_ctl/worker_runner.py +14 -12
- package/runtime/python/okstra_ctl/workflow.py +2 -1
- package/runtime/python/okstra_ctl/worktree.py +3 -2
- package/runtime/python/okstra_ctl/wrapper_status.py +4 -2
- package/runtime/python/okstra_ctl/write_policy.py +4 -2
- package/runtime/python/okstra_token_usage/antigravity.py +39 -12
- package/runtime/python/okstra_token_usage/collect.py +90 -38
- package/runtime/python/okstra_token_usage/grok.py +127 -0
- package/runtime/schemas/final-report-v2.0.schema.json +9 -0
- package/runtime/schemas/final-report-v3.0.schema.json +10219 -0
- package/runtime/schemas/report-narrative-v3.0.schema.json +30 -0
- package/runtime/schemas/report-synthesis-packet-v1.0.schema.json +140 -0
- package/runtime/skills/okstra-brief-gen/SKILL.md +9 -7
- package/runtime/skills/okstra-code-review/SKILL.md +21 -11
- package/runtime/skills/okstra-container-build/SKILL.md +18 -18
- package/runtime/skills/okstra-inspect/SKILL.md +12 -11
- package/runtime/skills/okstra-inspect/facets/error-zip.md +8 -8
- package/runtime/skills/okstra-inspect/facets/errors.md +2 -2
- package/runtime/skills/okstra-inspect/facets/history.md +9 -14
- package/runtime/skills/okstra-inspect/facets/logs.md +2 -2
- package/runtime/skills/okstra-inspect/facets/recap.md +5 -5
- package/runtime/skills/okstra-inspect/facets/report.md +6 -10
- package/runtime/skills/okstra-inspect/facets/status.md +9 -8
- package/runtime/skills/okstra-inspect/facets/time.md +3 -3
- package/runtime/skills/okstra-manager/SKILL.md +16 -14
- package/runtime/skills/okstra-memory/SKILL.md +3 -3
- package/runtime/skills/okstra-pr-gen/SKILL.md +5 -4
- package/runtime/skills/okstra-rollup/SKILL.md +6 -16
- package/runtime/skills/okstra-run/SKILL.md +7 -7
- package/runtime/skills/okstra-schedule-gen/SKILL.md +21 -17
- package/runtime/skills/okstra-setup/SKILL.md +21 -13
- package/runtime/skills/okstra-setup/references/project-config.md +2 -2
- package/runtime/skills/okstra-usage/SKILL.md +10 -10
- package/runtime/skills/okstra-user-response/SKILL.md +78 -107
- package/runtime/templates/report-writer-prompt-preamble.md +29 -19
- package/runtime/templates/reports/html/macros/forms.html +6 -4
- package/runtime/templates/reports/schedule.template.md +4 -4
- package/runtime/templates/worker-error-contract.md +17 -29
- package/runtime/validators/validate-run.py +636 -72
- package/runtime/validators/validate_session_conformance.py +43 -7
|
@@ -16,6 +16,7 @@ state passing, and are read once at the start.
|
|
|
16
16
|
from __future__ import annotations
|
|
17
17
|
|
|
18
18
|
import hashlib
|
|
19
|
+
from argparse import Action, ArgumentError, ArgumentParser, Namespace
|
|
19
20
|
import json
|
|
20
21
|
import os
|
|
21
22
|
import re
|
|
@@ -32,7 +33,11 @@ from okstra_project import project_json_path, upsert_project_json
|
|
|
32
33
|
from okstra_project.state import slugify
|
|
33
34
|
from . import fix_cycles
|
|
34
35
|
from .analysis_packet import build_analysis_packet
|
|
35
|
-
from .stage_ledger import
|
|
36
|
+
from .stage_ledger import (
|
|
37
|
+
build_stage_ledger,
|
|
38
|
+
render_stage_ledger,
|
|
39
|
+
stage_ledger_notice,
|
|
40
|
+
)
|
|
36
41
|
from .analysis_inputs import (
|
|
37
42
|
ANALYSIS_TASK_TYPES,
|
|
38
43
|
AnalysisInputError,
|
|
@@ -109,7 +114,10 @@ from .legacy_model_selection import (
|
|
|
109
114
|
serialize_host_session_context,
|
|
110
115
|
)
|
|
111
116
|
from .model_defaults import ModelDefaultScopes
|
|
112
|
-
from .worker_prompt_policy import
|
|
117
|
+
from .worker_prompt_policy import (
|
|
118
|
+
ANALYSIS_DUTY_BY_TASK_TYPE,
|
|
119
|
+
critic_assignment_ref,
|
|
120
|
+
)
|
|
113
121
|
from .models import (
|
|
114
122
|
UnknownProviderError,
|
|
115
123
|
default_model,
|
|
@@ -137,6 +145,11 @@ from .agent_invocation import (
|
|
|
137
145
|
verify_agent_invocation,
|
|
138
146
|
)
|
|
139
147
|
from .report_contract import CURRENT_REPORT_SCHEMA_VERSION
|
|
148
|
+
from .json_boundary import (
|
|
149
|
+
JsonBoundaryError,
|
|
150
|
+
load_owned_object,
|
|
151
|
+
write_owned_object_atomic,
|
|
152
|
+
)
|
|
140
153
|
from .path_resolve import relative_to_project_root, resolve_user_file
|
|
141
154
|
from .render import (
|
|
142
155
|
apply_lead_prompt_defaults,
|
|
@@ -250,8 +263,8 @@ def _load_final_report_data_if_present(report_path: Path) -> tuple[Path, dict] |
|
|
|
250
263
|
if not data_path.is_file():
|
|
251
264
|
return None
|
|
252
265
|
try:
|
|
253
|
-
data =
|
|
254
|
-
except
|
|
266
|
+
data = load_owned_object(data_path, artifact="approved plan final report")
|
|
267
|
+
except JsonBoundaryError as exc:
|
|
255
268
|
raise PrepareError(
|
|
256
269
|
f"approved plan data.json is invalid JSON: {data_path}: {exc}"
|
|
257
270
|
) from exc
|
|
@@ -664,7 +677,6 @@ def _record_start(
|
|
|
664
677
|
"""record_start hook 호출. okstra-central.sh 의 bash wrapper 와 같은 동작
|
|
665
678
|
이지만 python 직접 호출이라 환경 변수 의존 없음.
|
|
666
679
|
"""
|
|
667
|
-
import json as _json
|
|
668
680
|
from datetime import datetime, timezone
|
|
669
681
|
from okstra_ctl import record_start
|
|
670
682
|
from .locks import central_lock
|
|
@@ -679,11 +691,15 @@ def _record_start(
|
|
|
679
691
|
(home / f).touch(exist_ok=True)
|
|
680
692
|
state_file = home / "state.json"
|
|
681
693
|
if not state_file.exists():
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
694
|
+
write_owned_object_atomic(
|
|
695
|
+
state_file,
|
|
696
|
+
{
|
|
697
|
+
"schemaVersion": "1",
|
|
698
|
+
"createdAt": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
|
|
699
|
+
"backfilledAt": None,
|
|
700
|
+
},
|
|
701
|
+
artifact="global state",
|
|
702
|
+
)
|
|
687
703
|
os.chmod(state_file, 0o600)
|
|
688
704
|
# 중앙 인덱스 락은 reconcile.py 와 공유하는 central_lock 단일 기준점을 쓴다.
|
|
689
705
|
# 직접 flock 을 손으로 깔면 락 파일/모드가 두 경로에서 갈라질 수 있다.
|
|
@@ -1526,8 +1542,8 @@ def validate_project_qa_commands(task_type: str, project_root: Path) -> None:
|
|
|
1526
1542
|
if not project_json.is_file():
|
|
1527
1543
|
return
|
|
1528
1544
|
try:
|
|
1529
|
-
project_meta =
|
|
1530
|
-
except (OSError,
|
|
1545
|
+
project_meta = load_owned_object(project_json, artifact="project config")
|
|
1546
|
+
except (OSError, JsonBoundaryError) as exc:
|
|
1531
1547
|
raise PrepareError(
|
|
1532
1548
|
f"project.json read failed at {project_json}: {exc}"
|
|
1533
1549
|
) from exc
|
|
@@ -1562,11 +1578,13 @@ def _apply_qa_waiver_if_requested(inp: "PrepareInputs", project_root: Path) -> N
|
|
|
1562
1578
|
):
|
|
1563
1579
|
if not manifest_path.is_file():
|
|
1564
1580
|
raise PrepareError(f"--qa-waiver: conformance manifest not found at {manifest_path}")
|
|
1565
|
-
manifest =
|
|
1581
|
+
manifest = load_owned_object(manifest_path, artifact="conformance manifest")
|
|
1566
1582
|
when = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
1567
1583
|
if not apply_qa_waiver(manifest, stage_key, reason, at=when):
|
|
1568
1584
|
raise PrepareError(f"--qa-waiver: stageKey {stage_key!r} not in manifest {manifest_path}")
|
|
1569
|
-
|
|
1585
|
+
write_owned_object_atomic(
|
|
1586
|
+
manifest_path, manifest, artifact="conformance manifest"
|
|
1587
|
+
)
|
|
1570
1588
|
|
|
1571
1589
|
|
|
1572
1590
|
def _clear_stale_stage_waiver(inp: "PrepareInputs", project_root: Path, stage: int) -> None:
|
|
@@ -1584,7 +1602,7 @@ def _clear_stale_stage_waiver(inp: "PrepareInputs", project_root: Path, stage: i
|
|
|
1584
1602
|
)
|
|
1585
1603
|
if not manifest_path.is_file():
|
|
1586
1604
|
return
|
|
1587
|
-
manifest =
|
|
1605
|
+
manifest = load_owned_object(manifest_path, artifact="conformance manifest")
|
|
1588
1606
|
entries = manifest.get("entries") if isinstance(manifest, dict) else None
|
|
1589
1607
|
if not isinstance(entries, list):
|
|
1590
1608
|
return
|
|
@@ -1604,8 +1622,8 @@ def _clear_stale_stage_waiver(inp: "PrepareInputs", project_root: Path, stage: i
|
|
|
1604
1622
|
if parsed is not None and parsed[0] == stage_key:
|
|
1605
1623
|
return # user intentionally waived this stage for this run
|
|
1606
1624
|
if clear_qa_waiver(manifest, stage_key):
|
|
1607
|
-
|
|
1608
|
-
|
|
1625
|
+
write_owned_object_atomic(
|
|
1626
|
+
manifest_path, manifest, artifact="conformance manifest"
|
|
1609
1627
|
)
|
|
1610
1628
|
|
|
1611
1629
|
|
|
@@ -2117,8 +2135,8 @@ def _read_model_defaults(path: Path) -> dict[str, tuple[str, ...]]:
|
|
|
2117
2135
|
if not path.is_file():
|
|
2118
2136
|
return {}
|
|
2119
2137
|
try:
|
|
2120
|
-
payload =
|
|
2121
|
-
except (OSError,
|
|
2138
|
+
payload = load_owned_object(path, artifact="model defaults config")
|
|
2139
|
+
except (OSError, JsonBoundaryError) as exc:
|
|
2122
2140
|
raise PrepareError(f"model defaults file is invalid: {path}: {exc}") from exc
|
|
2123
2141
|
raw_defaults = payload.get("modelDefaults", {}) if isinstance(payload, dict) else {}
|
|
2124
2142
|
if not isinstance(raw_defaults, dict):
|
|
@@ -2504,6 +2522,7 @@ def _resolve_model_bindings(
|
|
|
2504
2522
|
worker_assignments,
|
|
2505
2523
|
critic_assignment,
|
|
2506
2524
|
translator_assignment,
|
|
2525
|
+
inp.task_type,
|
|
2507
2526
|
)
|
|
2508
2527
|
worker_by_provider = {
|
|
2509
2528
|
row.provider: row for row in worker_assignments
|
|
@@ -2653,6 +2672,7 @@ def _project_model_bindings(
|
|
|
2653
2672
|
workers,
|
|
2654
2673
|
critic_assignment,
|
|
2655
2674
|
_role_assignment(resolved=translator, role="translator"),
|
|
2675
|
+
inp.task_type,
|
|
2656
2676
|
)
|
|
2657
2677
|
worker_by_provider = {row.provider: row for row in workers}
|
|
2658
2678
|
return _ModelBindings(
|
|
@@ -2892,6 +2912,7 @@ def _build_invocation_assignments(
|
|
|
2892
2912
|
workers: tuple[RoleAssignment, ...],
|
|
2893
2913
|
critic: RoleAssignment | None,
|
|
2894
2914
|
translator: RoleAssignment,
|
|
2915
|
+
task_type: str,
|
|
2895
2916
|
) -> dict[str, dict[str, object]]:
|
|
2896
2917
|
assignments = {"lead": lead.to_model_payload()}
|
|
2897
2918
|
for assignment in workers:
|
|
@@ -2902,8 +2923,11 @@ def _build_invocation_assignments(
|
|
|
2902
2923
|
assignment.to_model_payload()
|
|
2903
2924
|
)
|
|
2904
2925
|
if critic is not None:
|
|
2905
|
-
|
|
2906
|
-
|
|
2926
|
+
# 한 run 이 도달할 수 있는 critic 은 한 종류다. 둘 다 등록하면 실행될
|
|
2927
|
+
# 수 없는 로스터 행이 남고, `_optional_worker_roles` 가 provider 로만
|
|
2928
|
+
# role 라벨을 만들기 때문에 두 행의 이름이 겹쳐 validate-run 이
|
|
2929
|
+
# `duplicate worker role detected` 로 런을 실패시킨다.
|
|
2930
|
+
assignments[critic_assignment_ref(task_type)] = critic.to_model_payload()
|
|
2907
2931
|
assignments["translator"] = translator.to_model_payload()
|
|
2908
2932
|
return assignments
|
|
2909
2933
|
|
|
@@ -3218,6 +3242,12 @@ def _write_instruction_set_sources(
|
|
|
3218
3242
|
render_reference_expectations(
|
|
3219
3243
|
str(inp.brief_path), str(instruction_set / "reference-expectations.md"), ctx,
|
|
3220
3244
|
)
|
|
3245
|
+
# 계획 phase 만 원장을 받는다. 원장은 "무엇이 이미 지어졌나" 를 계획 저작
|
|
3246
|
+
# 쪽에 알려 주는 것이므로, 계획을 쓰지 않는 phase 에는 실을 자리가 없다.
|
|
3247
|
+
stage_ledger = (
|
|
3248
|
+
build_stage_ledger(Path(ctx["TASK_MANIFEST_PATH"]).parent)
|
|
3249
|
+
if inp.task_type == "implementation-planning" else None
|
|
3250
|
+
)
|
|
3221
3251
|
packet = build_analysis_packet(
|
|
3222
3252
|
task_key=ctx["TASK_KEY"],
|
|
3223
3253
|
task_type=ctx["TASK_TYPE"],
|
|
@@ -3232,11 +3262,8 @@ def _write_instruction_set_sources(
|
|
|
3232
3262
|
instruction_set_relative_path=ctx["INSTRUCTION_SET_RELATIVE_PATH"],
|
|
3233
3263
|
fix_history_text=fix_cycles.packet_summary(
|
|
3234
3264
|
fix_cycles.read_rows(Path(ctx["TASK_MANIFEST_PATH"]).parent)),
|
|
3235
|
-
stage_ledger_json=(
|
|
3236
|
-
|
|
3237
|
-
build_stage_ledger(Path(ctx["TASK_MANIFEST_PATH"]).parent))
|
|
3238
|
-
if inp.task_type == "implementation-planning" else ""
|
|
3239
|
-
),
|
|
3265
|
+
stage_ledger_json=render_stage_ledger(stage_ledger),
|
|
3266
|
+
stage_ledger_notice=stage_ledger_notice(stage_ledger),
|
|
3240
3267
|
)
|
|
3241
3268
|
if inp.task_type in ANALYSIS_TASK_TYPES:
|
|
3242
3269
|
packet += (
|
|
@@ -3311,9 +3338,10 @@ def _render_lead_prompt_and_snapshot(
|
|
|
3311
3338
|
inp.task_type,
|
|
3312
3339
|
installed_version(),
|
|
3313
3340
|
)
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
|
|
3341
|
+
write_owned_object_atomic(
|
|
3342
|
+
Path(ctx["FINAL_REPORT_SCHEMA_PATH"]),
|
|
3343
|
+
_excerpt,
|
|
3344
|
+
artifact="final report schema excerpt",
|
|
3317
3345
|
)
|
|
3318
3346
|
except Exception: # noqa: BLE001 — advisory artifact; never fail prep over it
|
|
3319
3347
|
pass
|
|
@@ -3323,7 +3351,9 @@ def _render_lead_prompt_and_snapshot(
|
|
|
3323
3351
|
json.loads(ctx["INVOCATION_ASSIGNMENTS_JSON"])["lead"]
|
|
3324
3352
|
)
|
|
3325
3353
|
identity = invocation_execution_identity_from_manifest(
|
|
3326
|
-
|
|
3354
|
+
load_owned_object(
|
|
3355
|
+
Path(ctx["RUN_MANIFEST_PATH"]), artifact="run manifest"
|
|
3356
|
+
),
|
|
3327
3357
|
assignment=assignment,
|
|
3328
3358
|
assignment_ref="lead",
|
|
3329
3359
|
duty_id="lead",
|
|
@@ -3476,8 +3506,8 @@ def _read_existing_manifest(manifest_path: Path) -> dict:
|
|
|
3476
3506
|
if not manifest_path.exists():
|
|
3477
3507
|
return {}
|
|
3478
3508
|
try:
|
|
3479
|
-
return
|
|
3480
|
-
except (OSError,
|
|
3509
|
+
return load_owned_object(manifest_path, artifact="run manifest")
|
|
3510
|
+
except (OSError, JsonBoundaryError):
|
|
3481
3511
|
return {}
|
|
3482
3512
|
|
|
3483
3513
|
|
|
@@ -3971,7 +4001,7 @@ def prepare_task_bundle(inp: PrepareInputs) -> PrepareOutputs:
|
|
|
3971
4001
|
try:
|
|
3972
4002
|
json.loads(runtime_resolution_json or "{}")
|
|
3973
4003
|
except json.JSONDecodeError as exc:
|
|
3974
|
-
raise PrepareError(f"invalid
|
|
4004
|
+
raise PrepareError(f"invalid runtime resolution data: {exc}") from exc
|
|
3975
4005
|
# Probed once here and reused below, so the gate and the manifest cannot
|
|
3976
4006
|
# disagree about which backend this run is on.
|
|
3977
4007
|
terminal_backend = detect_terminal_backend()
|
|
@@ -4286,11 +4316,77 @@ def prepare_task_bundle(inp: PrepareInputs) -> PrepareOutputs:
|
|
|
4286
4316
|
)
|
|
4287
4317
|
|
|
4288
4318
|
|
|
4319
|
+
class _StoreRuntimeSingleton(Action):
|
|
4320
|
+
def __call__(
|
|
4321
|
+
self,
|
|
4322
|
+
parser: ArgumentParser,
|
|
4323
|
+
namespace: Namespace,
|
|
4324
|
+
values: object,
|
|
4325
|
+
option_string: str | None = None,
|
|
4326
|
+
) -> None:
|
|
4327
|
+
seen = getattr(namespace, "_runtime_singletons_seen", frozenset())
|
|
4328
|
+
if self.dest in seen:
|
|
4329
|
+
raise ArgumentError(
|
|
4330
|
+
self, f"{option_string} may be supplied only once"
|
|
4331
|
+
)
|
|
4332
|
+
setattr(namespace, self.dest, True if self.nargs == 0 else values)
|
|
4333
|
+
setattr(namespace, "_runtime_singletons_seen", seen | {self.dest})
|
|
4334
|
+
|
|
4335
|
+
|
|
4336
|
+
def _add_runtime_resolution_arguments(parser: ArgumentParser) -> None:
|
|
4337
|
+
runtime_ids = (*default_host_registry().ids(), "all")
|
|
4338
|
+
parser.add_argument(
|
|
4339
|
+
"--lead-runtime",
|
|
4340
|
+
action=_StoreRuntimeSingleton,
|
|
4341
|
+
default="claude-code",
|
|
4342
|
+
choices=runtime_ids,
|
|
4343
|
+
dest="lead_runtime",
|
|
4344
|
+
help=(
|
|
4345
|
+
"Lead runtime adapter. Default: claude-code. `codex` renders "
|
|
4346
|
+
"Codex CLI artifact bundles; `antigravity` renders Antigravity CLI "
|
|
4347
|
+
"artifact bundles; `external` renders neutral bootstrap bundles for "
|
|
4348
|
+
"manifest inspection until a dispatch backend is enabled."
|
|
4349
|
+
),
|
|
4350
|
+
)
|
|
4351
|
+
parser.add_argument(
|
|
4352
|
+
"--lead-runtime-request", action=_StoreRuntimeSingleton, default=""
|
|
4353
|
+
)
|
|
4354
|
+
parser.add_argument(
|
|
4355
|
+
"--runtime-resolution-launch-mode",
|
|
4356
|
+
action=_StoreRuntimeSingleton,
|
|
4357
|
+
choices=("lead", "team", "catalog"),
|
|
4358
|
+
default="",
|
|
4359
|
+
)
|
|
4360
|
+
parser.add_argument(
|
|
4361
|
+
"--runtime-resolution-install-target", action="append", default=[]
|
|
4362
|
+
)
|
|
4363
|
+
for flag in (
|
|
4364
|
+
"--runtime-resolution-host",
|
|
4365
|
+
"--runtime-resolution-reason",
|
|
4366
|
+
"--runtime-resolution-fallback-from",
|
|
4367
|
+
):
|
|
4368
|
+
parser.add_argument(flag, action=_StoreRuntimeSingleton, default="")
|
|
4369
|
+
parser.add_argument(
|
|
4370
|
+
"--runtime-resolution-confidence",
|
|
4371
|
+
action=_StoreRuntimeSingleton,
|
|
4372
|
+
choices=("none", "low", "medium", "high"),
|
|
4373
|
+
default="",
|
|
4374
|
+
)
|
|
4375
|
+
parser.add_argument("--runtime-resolution-warning", action="append", default=[])
|
|
4376
|
+
parser.add_argument(
|
|
4377
|
+
"--runtime-resolution-catalog-hosts-present",
|
|
4378
|
+
action=_StoreRuntimeSingleton,
|
|
4379
|
+
nargs=0,
|
|
4380
|
+
default=False,
|
|
4381
|
+
)
|
|
4382
|
+
parser.add_argument(
|
|
4383
|
+
"--runtime-resolution-catalog-host-id", action="append", default=[]
|
|
4384
|
+
)
|
|
4385
|
+
|
|
4386
|
+
|
|
4289
4387
|
def build_prepare_argument_parser():
|
|
4290
4388
|
"""Build the shared flat-argument parser used by public prepare shims."""
|
|
4291
|
-
|
|
4292
|
-
|
|
4293
|
-
p = argparse.ArgumentParser()
|
|
4389
|
+
p = ArgumentParser()
|
|
4294
4390
|
p.add_argument("--workspace-root", required=True)
|
|
4295
4391
|
p.add_argument("--project-root", required=True)
|
|
4296
4392
|
p.add_argument("--project-id", required=True)
|
|
@@ -4332,30 +4428,7 @@ def build_prepare_argument_parser():
|
|
|
4332
4428
|
p.add_argument("--worker-model", default="", dest="worker_models_raw")
|
|
4333
4429
|
p.add_argument("--report-writer-provider", default="")
|
|
4334
4430
|
p.add_argument("--report-writer-model", default="")
|
|
4335
|
-
p
|
|
4336
|
-
"--lead-runtime",
|
|
4337
|
-
default="claude-code",
|
|
4338
|
-
choices=list(default_host_registry().ids()),
|
|
4339
|
-
dest="lead_runtime",
|
|
4340
|
-
help=(
|
|
4341
|
-
"Lead runtime adapter. Default: claude-code. `codex` renders "
|
|
4342
|
-
"Codex CLI artifact bundles; `antigravity` renders Antigravity CLI "
|
|
4343
|
-
"artifact bundles; `external` renders neutral bootstrap bundles for "
|
|
4344
|
-
"manifest inspection until a dispatch backend is enabled."
|
|
4345
|
-
),
|
|
4346
|
-
)
|
|
4347
|
-
p.add_argument(
|
|
4348
|
-
"--lead-runtime-request",
|
|
4349
|
-
default="",
|
|
4350
|
-
dest="lead_runtime_request",
|
|
4351
|
-
help="Original lead runtime request before Node-side resolution, e.g. auto.",
|
|
4352
|
-
)
|
|
4353
|
-
p.add_argument(
|
|
4354
|
-
"--runtime-resolution-json",
|
|
4355
|
-
default="",
|
|
4356
|
-
dest="runtime_resolution_json",
|
|
4357
|
-
help="Node-side runtime resolution payload serialized as JSON.",
|
|
4358
|
-
)
|
|
4431
|
+
_add_runtime_resolution_arguments(p)
|
|
4359
4432
|
p.add_argument("--executor", default="")
|
|
4360
4433
|
p.add_argument("--critic", default="")
|
|
4361
4434
|
p.add_argument("--related-tasks", default="", dest="related_tasks_raw")
|
|
@@ -4470,6 +4543,51 @@ def build_prepare_argument_parser():
|
|
|
4470
4543
|
return p
|
|
4471
4544
|
|
|
4472
4545
|
|
|
4546
|
+
def _runtime_resolution_json_from_args(args: Namespace) -> str:
|
|
4547
|
+
values = {
|
|
4548
|
+
"launch mode": args.runtime_resolution_launch_mode,
|
|
4549
|
+
"host": args.runtime_resolution_host,
|
|
4550
|
+
"confidence": args.runtime_resolution_confidence,
|
|
4551
|
+
"reason": args.runtime_resolution_reason,
|
|
4552
|
+
}
|
|
4553
|
+
optional_values = (
|
|
4554
|
+
args.runtime_resolution_install_target,
|
|
4555
|
+
args.runtime_resolution_fallback_from,
|
|
4556
|
+
args.runtime_resolution_warning,
|
|
4557
|
+
args.runtime_resolution_catalog_hosts_present,
|
|
4558
|
+
args.runtime_resolution_catalog_host_id,
|
|
4559
|
+
)
|
|
4560
|
+
if not any(values.values()) and not any(optional_values):
|
|
4561
|
+
return "{}"
|
|
4562
|
+
missing = [name for name, value in values.items() if not value]
|
|
4563
|
+
if missing:
|
|
4564
|
+
raise PrepareError(
|
|
4565
|
+
"incomplete runtime resolution arguments: " + ", ".join(missing)
|
|
4566
|
+
)
|
|
4567
|
+
if (
|
|
4568
|
+
args.runtime_resolution_catalog_host_id
|
|
4569
|
+
and not args.runtime_resolution_catalog_hosts_present
|
|
4570
|
+
):
|
|
4571
|
+
raise PrepareError(
|
|
4572
|
+
"runtime resolution catalog host IDs require the presence marker"
|
|
4573
|
+
)
|
|
4574
|
+
payload: dict[str, object] = {
|
|
4575
|
+
"ok": True,
|
|
4576
|
+
"requestedRuntime": args.lead_runtime_request or args.lead_runtime,
|
|
4577
|
+
"resolvedRuntime": args.lead_runtime,
|
|
4578
|
+
"launchMode": args.runtime_resolution_launch_mode,
|
|
4579
|
+
"installTargets": args.runtime_resolution_install_target,
|
|
4580
|
+
"host": args.runtime_resolution_host,
|
|
4581
|
+
"confidence": args.runtime_resolution_confidence,
|
|
4582
|
+
"reason": args.runtime_resolution_reason,
|
|
4583
|
+
"fallbackFrom": args.runtime_resolution_fallback_from or None,
|
|
4584
|
+
"warnings": args.runtime_resolution_warning,
|
|
4585
|
+
}
|
|
4586
|
+
if args.runtime_resolution_catalog_hosts_present:
|
|
4587
|
+
payload["catalogHostIds"] = args.runtime_resolution_catalog_host_id
|
|
4588
|
+
return json.dumps(payload, ensure_ascii=False)
|
|
4589
|
+
|
|
4590
|
+
|
|
4473
4591
|
def main(argv: list[str]) -> int:
|
|
4474
4592
|
"""Parse one public prepare invocation and emit its rendered bundle."""
|
|
4475
4593
|
args = build_prepare_argument_parser().parse_args(argv)
|
|
@@ -4520,7 +4638,8 @@ def main(argv: list[str]) -> int:
|
|
|
4520
4638
|
host_session_context = deserialize_host_session_context(
|
|
4521
4639
|
args.host_session_context_json
|
|
4522
4640
|
)
|
|
4523
|
-
|
|
4641
|
+
runtime_resolution_json = _runtime_resolution_json_from_args(args)
|
|
4642
|
+
except (ModelSelectionInputError, PrepareError) as exc:
|
|
4524
4643
|
print(str(exc), file=__import__("sys").stderr)
|
|
4525
4644
|
return 1
|
|
4526
4645
|
if not args.host_session_context_json:
|
|
@@ -4560,7 +4679,7 @@ def main(argv: list[str]) -> int:
|
|
|
4560
4679
|
report_writer_model=args.report_writer_model,
|
|
4561
4680
|
lead_runtime=args.lead_runtime,
|
|
4562
4681
|
lead_runtime_request=args.lead_runtime_request,
|
|
4563
|
-
runtime_resolution_json=
|
|
4682
|
+
runtime_resolution_json=runtime_resolution_json,
|
|
4564
4683
|
executor=args.executor,
|
|
4565
4684
|
critic=args.critic,
|
|
4566
4685
|
related_tasks_raw=args.related_tasks_raw,
|
|
@@ -4628,17 +4747,17 @@ def _lead_launch_payload(ctx: dict[str, object]) -> dict[str, object]:
|
|
|
4628
4747
|
project_root = Path(str(ctx["PROJECT_ROOT"]))
|
|
4629
4748
|
run_manifest_path = Path(str(ctx["RUN_MANIFEST_PATH"]))
|
|
4630
4749
|
try:
|
|
4631
|
-
manifest =
|
|
4750
|
+
manifest = load_owned_object(run_manifest_path, artifact="run manifest")
|
|
4632
4751
|
resources = manifest["resources"]
|
|
4633
4752
|
assignment = agent_model_assignment_from_payload(
|
|
4634
4753
|
manifest["invocationAssignments"]["lead"]
|
|
4635
4754
|
)
|
|
4636
4755
|
prompt_file = project_root / resources["leadExecutionPromptPath"]
|
|
4637
4756
|
metadata_path = project_root / resources["leadPromptMetadataPath"]
|
|
4638
|
-
metadata =
|
|
4757
|
+
metadata = load_owned_object(metadata_path, artifact="lead prompt metadata")
|
|
4639
4758
|
if metadata["prompt"]["path"] != resources["leadExecutionPromptPath"]:
|
|
4640
4759
|
raise KeyError("lead prompt resource does not match metadata")
|
|
4641
|
-
except (OSError,
|
|
4760
|
+
except (OSError, JsonBoundaryError, KeyError, TypeError, AgentInvocationError) as exc:
|
|
4642
4761
|
raise PrepareError("lead invocation authority is incomplete") from exc
|
|
4643
4762
|
errors = verify_agent_invocation(
|
|
4644
4763
|
metadata_path,
|
|
@@ -19,6 +19,7 @@ from okstra_ctl.paths import okstra_home, resolve_under_root
|
|
|
19
19
|
from okstra_ctl.reconcile import NON_TERMINAL_RECENT_STATUSES
|
|
20
20
|
from okstra_ctl.task_target import project_rel
|
|
21
21
|
from okstra_ctl.workflow import PHASE_SEQUENCE
|
|
22
|
+
from okstra_ctl.json_boundary import JsonBoundaryError, load_owned_object
|
|
22
23
|
|
|
23
24
|
# 불변식 이름은 여기서만 정의한다. `INVARIANTS` 와 방출부가 같은 상수를 봐야,
|
|
24
25
|
# 이름을 하나 더할 때 한쪽만 고치고도 스위트가 green 인 상태가 생기지 않는다 —
|
|
@@ -74,8 +75,8 @@ _RECORDED_WORKER_STATUSES = frozenset(
|
|
|
74
75
|
|
|
75
76
|
def _load_json(path: Path) -> dict:
|
|
76
77
|
try:
|
|
77
|
-
loaded =
|
|
78
|
-
except
|
|
78
|
+
loaded = load_owned_object(path, artifact="run audit input")
|
|
79
|
+
except JsonBoundaryError:
|
|
79
80
|
return {}
|
|
80
81
|
return loaded if isinstance(loaded, dict) else {}
|
|
81
82
|
|
|
@@ -26,6 +26,7 @@ from typing import Iterator, Optional
|
|
|
26
26
|
from okstra_project.dirs import okstra_home
|
|
27
27
|
|
|
28
28
|
from .path_hints import compact_run_context, hydrate_run_context
|
|
29
|
+
from .json_boundary import JsonBoundaryError, load_owned_object, write_owned_object_atomic
|
|
29
30
|
from .paths import compute_run_paths, task_runs_dir
|
|
30
31
|
|
|
31
32
|
|
|
@@ -66,8 +67,8 @@ def latest_run_inputs(
|
|
|
66
67
|
return {}
|
|
67
68
|
candidates.sort(key=lambda x: -x[0])
|
|
68
69
|
try:
|
|
69
|
-
data =
|
|
70
|
-
except
|
|
70
|
+
data = load_owned_object(candidates[0][1], artifact="run context")
|
|
71
|
+
except JsonBoundaryError:
|
|
71
72
|
return {}
|
|
72
73
|
inputs = data.get("inputs")
|
|
73
74
|
return inputs if isinstance(inputs, dict) else {}
|
|
@@ -129,11 +130,7 @@ def consumers_mutex(plan_run_root: Path) -> Iterator[None]:
|
|
|
129
130
|
|
|
130
131
|
|
|
131
132
|
def _atomic_write_json(path: Path, payload: dict) -> None:
|
|
132
|
-
path
|
|
133
|
-
tmp = path.with_suffix(path.suffix + ".tmp")
|
|
134
|
-
tmp.write_text(json.dumps(payload, indent=2, ensure_ascii=False) + "\n",
|
|
135
|
-
encoding="utf-8")
|
|
136
|
-
os.replace(tmp, path)
|
|
133
|
+
write_owned_object_atomic(path, payload, artifact="run context")
|
|
137
134
|
|
|
138
135
|
|
|
139
136
|
def _run_context_filename(task_type_segment: str, seq: str) -> str:
|
|
@@ -240,7 +237,7 @@ def read_run_context(run_manifests_dir: Path, task_type_segment: str,
|
|
|
240
237
|
path = Path(run_manifests_dir) / _run_context_filename(task_type_segment, seq)
|
|
241
238
|
if not path.is_file():
|
|
242
239
|
return None
|
|
243
|
-
payload =
|
|
240
|
+
payload = load_owned_object(path, artifact="run context")
|
|
244
241
|
context = hydrate_run_context(payload)
|
|
245
242
|
analysis = payload.get("analysis")
|
|
246
243
|
if isinstance(analysis, dict):
|
|
@@ -268,4 +265,4 @@ def read_run_inputs(run_manifests_dir: Path, task_type_segment: str,
|
|
|
268
265
|
path = Path(run_manifests_dir) / _run_inputs_filename(task_type_segment, seq)
|
|
269
266
|
if not path.is_file():
|
|
270
267
|
return None
|
|
271
|
-
return
|
|
268
|
+
return load_owned_object(path, artifact="run inputs")
|
|
@@ -8,14 +8,12 @@ slim row 에 raw 로 보존한다 — taskGroup/taskId 와 동일한 이유다.
|
|
|
8
8
|
"""
|
|
9
9
|
from __future__ import annotations
|
|
10
10
|
|
|
11
|
-
import json
|
|
12
|
-
import os
|
|
13
11
|
from pathlib import Path
|
|
14
12
|
from typing import Optional
|
|
15
13
|
|
|
16
14
|
from .ids import build_run_id, parse_run_id
|
|
17
15
|
from .invocation import invocation_path
|
|
18
|
-
from .jsonl import read_jsonl
|
|
16
|
+
from .jsonl import read_jsonl, rewrite_jsonl
|
|
19
17
|
|
|
20
18
|
DERIVED_FIELDS = ("taskType", "runSeq", "taskKey", "invocationFile")
|
|
21
19
|
|
|
@@ -103,11 +101,7 @@ def _rewrite_slim(path: Path) -> int:
|
|
|
103
101
|
rows = read_jsonl(path)
|
|
104
102
|
if not rows:
|
|
105
103
|
return 0
|
|
106
|
-
|
|
107
|
-
with tmp.open("w") as f:
|
|
108
|
-
for r in rows:
|
|
109
|
-
f.write(json.dumps(slim_run_row(r), separators=(",", ":")) + "\n")
|
|
110
|
-
os.replace(tmp, path)
|
|
104
|
+
rewrite_jsonl(path, [slim_run_row(row) for row in rows])
|
|
111
105
|
return len(rows)
|
|
112
106
|
|
|
113
107
|
|
|
@@ -9,6 +9,7 @@ from pathlib import Path
|
|
|
9
9
|
from typing import Any
|
|
10
10
|
|
|
11
11
|
from .md_table import is_separator_row, split_pipe_row
|
|
12
|
+
from .json_boundary import external_user_json_source, load_external_json
|
|
12
13
|
|
|
13
14
|
|
|
14
15
|
_ROOT_FIELDS = {"schemaVersion", "tasks"}
|
|
@@ -404,8 +405,10 @@ def _strict_json_object(pairs: list[tuple[str, Any]]) -> dict[str, Any]:
|
|
|
404
405
|
|
|
405
406
|
|
|
406
407
|
def load_schedule_selection(path: Path) -> tuple[SelectionTask, ...]:
|
|
407
|
-
|
|
408
|
-
|
|
408
|
+
# 외부 입력: okstra-schedule 사용자가 선택한 작업 집합 문서다.
|
|
409
|
+
payload = load_external_json(
|
|
410
|
+
external_user_json_source(path),
|
|
411
|
+
artifact="schedule selection",
|
|
409
412
|
object_pairs_hook=_strict_json_object,
|
|
410
413
|
)
|
|
411
414
|
root = _require_object(payload, "selection")
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
"""Build a task-type-scoped excerpt of
|
|
1
|
+
"""Build a task-type-scoped excerpt of a versioned final-report schema.
|
|
2
2
|
|
|
3
|
-
The full schema
|
|
3
|
+
The full schema carries the
|
|
4
4
|
deliverable property blocks for ALL task-types (``errorAnalysis``, the three
|
|
5
5
|
read-only analysis blocks, ``implementationPlanning``, ``releaseHandoff``,
|
|
6
6
|
``implementation``, and ``finalVerification``) plus a
|
|
7
7
|
``$defs`` library (~38% of the file) shared across them. A single run only
|
|
8
|
-
authors ONE task-type's
|
|
8
|
+
authors ONE task-type's narrative, so the report-writer worker only needs
|
|
9
9
|
the common structure + its own task-type's block + the ``$defs`` those
|
|
10
10
|
reach.
|
|
11
11
|
|
|
@@ -16,7 +16,7 @@ repo/installed schema (whose `schemas/...` path is not even resolvable
|
|
|
16
16
|
from inside a consumer project's task bundle).
|
|
17
17
|
|
|
18
18
|
The excerpt is ADVISORY — a reading aid for the author. Validation always
|
|
19
|
-
runs against the FULL schema
|
|
19
|
+
runs against the matching FULL schema, so
|
|
20
20
|
the excerpt never gates correctness; it only trims what the worker reads.
|
|
21
21
|
"""
|
|
22
22
|
from __future__ import annotations
|
|
@@ -27,8 +27,10 @@ from collections.abc import Sequence
|
|
|
27
27
|
from dataclasses import dataclass
|
|
28
28
|
from pathlib import Path
|
|
29
29
|
|
|
30
|
+
from .json_boundary import JsonBoundaryError, load_owned_object
|
|
31
|
+
|
|
30
32
|
from .final_report_schema import load_schema_version
|
|
31
|
-
from .report_contract import
|
|
33
|
+
from .report_contract import TASK_TYPE_DATA_PROPERTY
|
|
32
34
|
|
|
33
35
|
|
|
34
36
|
_ALL_PER_TYPE_PROPERTIES = frozenset(TASK_TYPE_DATA_PROPERTY.values())
|
|
@@ -160,17 +162,22 @@ def excerpt_contract_skew(
|
|
|
160
162
|
if not installed:
|
|
161
163
|
return None
|
|
162
164
|
try:
|
|
163
|
-
excerpt =
|
|
164
|
-
except
|
|
165
|
+
excerpt = load_owned_object(excerpt_path, artifact="schema excerpt")
|
|
166
|
+
except JsonBoundaryError:
|
|
165
167
|
return None
|
|
166
168
|
if not isinstance(excerpt, dict):
|
|
167
169
|
return None
|
|
168
170
|
cut_from = excerpt_cut_from_version(excerpt)
|
|
169
171
|
if not cut_from or cut_from == installed:
|
|
170
172
|
return None
|
|
173
|
+
schema_version = (
|
|
174
|
+
excerpt.get("properties", {})
|
|
175
|
+
.get("schemaVersion", {})
|
|
176
|
+
.get("const", "2.0")
|
|
177
|
+
)
|
|
171
178
|
try:
|
|
172
179
|
fresh = build_schema_excerpt(
|
|
173
|
-
load_schema_version(
|
|
180
|
+
load_schema_version(str(schema_version)), task_type, installed
|
|
174
181
|
)
|
|
175
182
|
except Exception: # noqa: BLE001 — an unloadable schema proves no drift
|
|
176
183
|
return None
|
|
@@ -50,13 +50,39 @@ class SessionTranscript:
|
|
|
50
50
|
결론과 okstra 자신의 기록이 그렇다. 잘린 도구 에코는 세부를 잃지만
|
|
51
51
|
잘린 결론은 사후 분석 전체를 잃는다.
|
|
52
52
|
"""
|
|
53
|
+
row = self._row(speaker, line)
|
|
54
|
+
self._show(row)
|
|
55
|
+
self._keep(row, capped=capped)
|
|
56
|
+
|
|
57
|
+
def write_event(
|
|
58
|
+
self, speaker: str, *, screen: list[str], archive: list[str]
|
|
59
|
+
) -> None:
|
|
60
|
+
"""한 이벤트의 두 투영을 각자의 목적지로 보낸다.
|
|
61
|
+
|
|
62
|
+
화면은 요약을 받고 기록은 본문까지 받는다. 둘 중 하나를 골라 양쪽에
|
|
63
|
+
쓰던 동안에는 pane 이 붙은 run 의 기록이 화면 투영으로 대체됐다 —
|
|
64
|
+
도구 결과가 `← done (23 bytes)` 한 줄로만 남고 그 23 바이트가 무엇이
|
|
65
|
+
었는지는 파일 어디에도 없었다. antigravity `view_file` 은 파일 내용이
|
|
66
|
+
아니라 `721 lines, 114306 bytes` 같은 요약을 돌려주므로, 본문이 빠진
|
|
67
|
+
기록에서는 46KB 를 읽은 호출과 잘린 호출이 같은 줄로 보인다.
|
|
68
|
+
"""
|
|
69
|
+
for line in screen:
|
|
70
|
+
self._show(self._row(speaker, line))
|
|
71
|
+
for line in archive:
|
|
72
|
+
self._keep(self._row(speaker, line), capped=True)
|
|
73
|
+
|
|
74
|
+
def _row(self, speaker: str, line: str) -> str:
|
|
53
75
|
# 패딩이 본문 앞 공백을 겸한다. 뒤에 공백을 한 칸 더 넣으면
|
|
54
76
|
# `[worker:grok]`(13칸) 줄이 두 칸이 되고 `[okstra]` 정렬이 깨진다.
|
|
55
77
|
label = f"[{speaker}]".ljust(_SPEAKER_WIDTH)
|
|
56
|
-
|
|
78
|
+
return f"{self._clock()} {label}{line}".rstrip()
|
|
79
|
+
|
|
80
|
+
def _show(self, row: str) -> None:
|
|
57
81
|
# 화면은 상한과 무관하다. 사람이 보고 있는 것을 잘라 낼 이유가 없다.
|
|
58
82
|
if self._live:
|
|
59
83
|
print(row, flush=True)
|
|
84
|
+
|
|
85
|
+
def _keep(self, row: str, *, capped: bool) -> None:
|
|
60
86
|
if not capped:
|
|
61
87
|
self._append(row)
|
|
62
88
|
return
|