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
|
@@ -36,6 +36,11 @@ import tempfile
|
|
|
36
36
|
from pathlib import Path
|
|
37
37
|
|
|
38
38
|
from okstra_ctl.session import resolve_inproc_lead_session_id
|
|
39
|
+
from okstra_ctl.json_boundary import (
|
|
40
|
+
JsonBoundaryError,
|
|
41
|
+
external_claude_team_json_source,
|
|
42
|
+
load_external_json,
|
|
43
|
+
)
|
|
39
44
|
|
|
40
45
|
|
|
41
46
|
def live_pane_ids() -> tuple[bool, set[str]]:
|
|
@@ -148,8 +153,12 @@ def _reconcile_config(cfg_path: Path, dry_run: bool, print_members: bool) -> int
|
|
|
148
153
|
line per non-lead member so the lead knows whom to ``shutdown_request``.
|
|
149
154
|
"""
|
|
150
155
|
try:
|
|
151
|
-
|
|
152
|
-
|
|
156
|
+
# 외부 입력: Claude Code가 소유하는 team roster 설정이다.
|
|
157
|
+
config = load_external_json(
|
|
158
|
+
external_claude_team_json_source(cfg_path),
|
|
159
|
+
artifact="Claude team configuration",
|
|
160
|
+
)
|
|
161
|
+
except JsonBoundaryError as exc:
|
|
153
162
|
print(f"team-reconcile: cannot read team config at {cfg_path}: {exc}",
|
|
154
163
|
file=sys.stderr)
|
|
155
164
|
return 1
|
|
@@ -14,7 +14,10 @@ import json
|
|
|
14
14
|
import sys
|
|
15
15
|
from pathlib import Path
|
|
16
16
|
|
|
17
|
+
from .fixed_text import line as _text_line
|
|
18
|
+
|
|
17
19
|
from okstra_ctl.paths import resolve_under_root, task_timeline_file
|
|
20
|
+
from okstra_ctl.json_boundary import load_owned_object
|
|
18
21
|
from okstra_project import read_task_key
|
|
19
22
|
from okstra_ctl.task_target import resolve_task_root
|
|
20
23
|
|
|
@@ -25,7 +28,7 @@ def time_rows_with_identity(run_root: Path) -> list[dict]:
|
|
|
25
28
|
|
|
26
29
|
state_path = run_root / "state" / "team-state.json"
|
|
27
30
|
try:
|
|
28
|
-
state =
|
|
31
|
+
state = load_owned_object(state_path, artifact="team state")
|
|
29
32
|
except (OSError, ValueError):
|
|
30
33
|
return []
|
|
31
34
|
return identity_rows_from_state(state if isinstance(state, dict) else {})
|
|
@@ -34,7 +37,7 @@ def time_rows_with_identity(run_root: Path) -> list[dict]:
|
|
|
34
37
|
def load_runs(task_root: Path) -> tuple[list[dict], bool]:
|
|
35
38
|
path = task_timeline_file(task_root)
|
|
36
39
|
try:
|
|
37
|
-
data =
|
|
40
|
+
data = load_owned_object(path, artifact="task timeline")
|
|
38
41
|
except (OSError, ValueError):
|
|
39
42
|
return [], True
|
|
40
43
|
runs = data.get("runs", []) if isinstance(data, dict) else []
|
|
@@ -96,7 +99,7 @@ def _read_state(project_root: Path, rel: str) -> dict | None:
|
|
|
96
99
|
if target is None:
|
|
97
100
|
return None
|
|
98
101
|
try:
|
|
99
|
-
state =
|
|
102
|
+
state = load_owned_object(target, artifact="team state")
|
|
100
103
|
except (OSError, ValueError):
|
|
101
104
|
return None
|
|
102
105
|
return state if isinstance(state, dict) else None
|
|
@@ -211,6 +214,48 @@ def aggregate_time(runs: list[dict], project_root: Path) -> dict:
|
|
|
211
214
|
}
|
|
212
215
|
|
|
213
216
|
|
|
217
|
+
def _worker_text(result: dict) -> str:
|
|
218
|
+
parts: list[str] = []
|
|
219
|
+
workers = result.get("perWorker")
|
|
220
|
+
if not isinstance(workers, dict):
|
|
221
|
+
return ""
|
|
222
|
+
for task_type, rows in workers.items():
|
|
223
|
+
for row in rows if isinstance(rows, list) else []:
|
|
224
|
+
if isinstance(row, dict):
|
|
225
|
+
parts.extend(("\n## Worker\n\n", _text_line("Task type", task_type), _text_line("Worker ID", row.get("workerId")), _text_line("Agents", ",".join(row.get("agents", [])) if isinstance(row.get("agents"), list) else None), _text_line("Runs", row.get("runs")), _text_line("Total ms", row.get("totalMs")), _text_line("Average ms", row.get("avgMs"))))
|
|
226
|
+
return "".join(parts)
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def _timeline_text(result: dict) -> str:
|
|
230
|
+
parts: list[str] = []
|
|
231
|
+
for row in result.get("perRunWallClock", []):
|
|
232
|
+
if isinstance(row, dict):
|
|
233
|
+
parts.extend(("\n## Run wall clock\n\n", _text_line("Run timestamp", row.get("runTimestamp")), _text_line("Task type", row.get("taskType")), _text_line("Wall clock ms", row.get("wallClockMs"))))
|
|
234
|
+
for timeline in result.get("phaseTimelines", []):
|
|
235
|
+
if not isinstance(timeline, dict):
|
|
236
|
+
continue
|
|
237
|
+
for phase in timeline.get("phases", []):
|
|
238
|
+
if isinstance(phase, dict):
|
|
239
|
+
parts.extend(("\n## Phase\n\n", _text_line("Run timestamp", timeline.get("runTimestamp")), _text_line("Task type", timeline.get("taskType")), _text_line("Phase", phase.get("phase")), _text_line("First at", phase.get("firstAt")), _text_line("Wall ms to next", phase.get("wallMsToNext"))))
|
|
240
|
+
return "".join(parts)
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def render_result_text(result: dict) -> str:
|
|
244
|
+
parts = ["# Okstra Time Input\n\n", _text_line("Task key", result.get("taskKey"))]
|
|
245
|
+
total = result.get("grandTotal") if isinstance(result.get("grandTotal"), dict) else {}
|
|
246
|
+
for key, label in (("runs", "Total runs"), ("leadMs", "Total lead ms"), ("workersMs", "Total workers ms"), ("cpuSumMs", "Total CPU sum ms")):
|
|
247
|
+
parts.append(_text_line(label, total.get(key)))
|
|
248
|
+
for row in result.get("byTaskType", []):
|
|
249
|
+
if not isinstance(row, dict):
|
|
250
|
+
continue
|
|
251
|
+
parts.extend(("\n## Task type\n\n", _text_line("Task type", row.get("taskType")), _text_line("Runs", row.get("runs")), _text_line("Lead ms", row.get("leadMs")), _text_line("Workers ms", row.get("workersMs")), _text_line("CPU sum ms", row.get("cpuSumMs"))))
|
|
252
|
+
parts.extend((_worker_text(result), _timeline_text(result)))
|
|
253
|
+
for row in result.get("unavailable", []):
|
|
254
|
+
if isinstance(row, dict):
|
|
255
|
+
parts.extend(("\n## Unavailable run\n\n", _text_line("Run timestamp", row.get("runTimestamp")), _text_line("Task type", row.get("taskType")), _text_line("Reason", row.get("reason"))))
|
|
256
|
+
return "".join(parts)
|
|
257
|
+
|
|
258
|
+
|
|
214
259
|
def main(argv: list[str] | None = None) -> int:
|
|
215
260
|
parser = argparse.ArgumentParser(
|
|
216
261
|
prog="okstra time-report",
|
|
@@ -219,13 +264,15 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
219
264
|
parser.add_argument("--project-root", default="")
|
|
220
265
|
parser.add_argument("--cwd", default=".")
|
|
221
266
|
parser.add_argument("--json", action="store_true", help="emit JSON (always on)")
|
|
267
|
+
parser.add_argument("--text", action="store_true", help="emit fixed text fields")
|
|
222
268
|
args = parser.parse_args(argv)
|
|
223
269
|
|
|
224
270
|
task_root, project_root = resolve_task_root(args.target, args.project_root, args.cwd)
|
|
225
271
|
runs, _ = load_runs(task_root)
|
|
226
272
|
result = {"ok": True, "taskKey": read_task_key(task_root)}
|
|
227
273
|
result.update(aggregate_time(runs, project_root))
|
|
228
|
-
|
|
274
|
+
output = render_result_text(result) if args.text else json.dumps(result, ensure_ascii=False, indent=2)
|
|
275
|
+
print(output, end="" if args.text else "\n")
|
|
229
276
|
return 0
|
|
230
277
|
|
|
231
278
|
|
|
@@ -13,6 +13,7 @@ from .execution_identity import (
|
|
|
13
13
|
stored_identity,
|
|
14
14
|
)
|
|
15
15
|
from .execution_manifest import read_execution_manifest
|
|
16
|
+
from .json_boundary import load_owned_object
|
|
16
17
|
|
|
17
18
|
|
|
18
19
|
def run_execution_manifest(run_root: Path) -> ExecutionManifest:
|
|
@@ -83,4 +84,4 @@ def _manifest_path(run_root: Path) -> Path:
|
|
|
83
84
|
|
|
84
85
|
|
|
85
86
|
def _load_json(path: Path) -> dict[str, Any]:
|
|
86
|
-
return
|
|
87
|
+
return load_owned_object(path, artifact="usage identity source")
|
|
@@ -14,6 +14,8 @@ from okstra_ctl.time_report import (
|
|
|
14
14
|
wall_clock_ms,
|
|
15
15
|
load_runs,
|
|
16
16
|
)
|
|
17
|
+
from okstra_ctl.json_boundary import load_owned_object
|
|
18
|
+
from okstra_ctl.fixed_text import line
|
|
17
19
|
from okstra_project import ResolverError, list_project_tasks, resolve_project_root
|
|
18
20
|
|
|
19
21
|
UTC = dt.timezone.utc
|
|
@@ -43,8 +45,8 @@ def _parse_timestamp(value: Any) -> dt.datetime | None:
|
|
|
43
45
|
|
|
44
46
|
def _project_id(project_root: Path) -> str:
|
|
45
47
|
try:
|
|
46
|
-
data =
|
|
47
|
-
|
|
48
|
+
data = load_owned_object(
|
|
49
|
+
project_root / ".okstra" / "project.json", artifact="project config"
|
|
48
50
|
)
|
|
49
51
|
except (OSError, ValueError):
|
|
50
52
|
return ""
|
|
@@ -214,6 +216,9 @@ def build_usage_report(project_root: Path, days: int, now: dt.datetime) -> dict:
|
|
|
214
216
|
rows = sorted(buckets.values(), key=lambda row: row["firstRunAt"])
|
|
215
217
|
for row in rows:
|
|
216
218
|
row["costUsd"] = round(_float_value(row["costUsd"]), 4)
|
|
219
|
+
row["collectionRate"] = (
|
|
220
|
+
row["collectedRuns"] / row["runs"] if row["runs"] else 0.0
|
|
221
|
+
)
|
|
217
222
|
totals = {
|
|
218
223
|
"runs": total_runs,
|
|
219
224
|
"collectedRuns": collected_runs,
|
|
@@ -264,18 +269,67 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
264
269
|
parser.add_argument("--project-root", default="")
|
|
265
270
|
parser.add_argument("--cwd", default=".")
|
|
266
271
|
parser.add_argument("--json", action="store_true", help="emit JSON (always on)")
|
|
272
|
+
parser.add_argument("--text", action="store_true", help="emit fixed text fields")
|
|
267
273
|
args = parser.parse_args(argv)
|
|
268
274
|
try:
|
|
269
275
|
project_root = Path(
|
|
270
276
|
resolve_project_root(explicit_root=args.project_root, cwd=args.cwd)
|
|
271
277
|
).resolve()
|
|
272
278
|
except ResolverError as exc:
|
|
273
|
-
|
|
279
|
+
payload = {"ok": False, "stage": "resolve", "reason": str(exc)}
|
|
280
|
+
print(render_usage_text(payload) if args.text else json.dumps(payload))
|
|
274
281
|
return 2
|
|
275
282
|
result = build_usage_report(project_root, args.days, utc_now())
|
|
276
|
-
print(
|
|
283
|
+
print(
|
|
284
|
+
render_usage_text(result)
|
|
285
|
+
if args.text
|
|
286
|
+
else json.dumps(result, ensure_ascii=False, indent=2)
|
|
287
|
+
)
|
|
277
288
|
return 0
|
|
278
289
|
|
|
279
290
|
|
|
291
|
+
def render_usage_text(payload: dict) -> str:
|
|
292
|
+
"""usage 모델 표면에 승인된 집계만 고정 순서로 투영한다."""
|
|
293
|
+
rows = ["Okstra usage\n"]
|
|
294
|
+
rows.append(line("Status", "ready" if payload.get("ok") else "error"))
|
|
295
|
+
rows.append(line("Project ID", payload.get("projectId")))
|
|
296
|
+
period = payload.get("range") if isinstance(payload.get("range"), dict) else {}
|
|
297
|
+
rows.append(line("Range days", period.get("days")))
|
|
298
|
+
runs = payload.get("runs") if isinstance(payload.get("runs"), dict) else {}
|
|
299
|
+
rows.append(line("Runs total", runs.get("total")))
|
|
300
|
+
rows.append(line("Runs collected", runs.get("collected")))
|
|
301
|
+
rows.append(line("Runs collection rate", runs.get("collectionRate")))
|
|
302
|
+
task_types = payload.get("byTaskType") if isinstance(payload.get("byTaskType"), list) else []
|
|
303
|
+
for index, item in enumerate(task_types, 1):
|
|
304
|
+
if not isinstance(item, dict):
|
|
305
|
+
continue
|
|
306
|
+
rows.append(line(f"Task type {index} name", item.get("taskType")))
|
|
307
|
+
rows.append(line(f"Task type {index} runs", item.get("runs")))
|
|
308
|
+
rows.append(line(f"Task type {index} collected runs", item.get("collectedRuns")))
|
|
309
|
+
rows.append(line(f"Task type {index} collection rate", item.get("collectionRate")))
|
|
310
|
+
rows.append(line(f"Task type {index} raw tokens", item.get("rawTokens")))
|
|
311
|
+
rows.append(line(f"Task type {index} billable tokens", item.get("billableTokens")))
|
|
312
|
+
rows.append(line(f"Task type {index} cost USD", item.get("costUsd")))
|
|
313
|
+
rows.append(line(f"Task type {index} CPU sum ms", item.get("cpuSumMs")))
|
|
314
|
+
rows.append(line(f"Task type {index} wall clock ms", item.get("wallClockMs")))
|
|
315
|
+
totals = payload.get("totals") if isinstance(payload.get("totals"), dict) else {}
|
|
316
|
+
for label, key in (("Raw tokens", "rawTokens"), ("Billable tokens", "billableTokens"),
|
|
317
|
+
("Cost USD", "costUsd"), ("CPU sum ms", "cpuSumMs"),
|
|
318
|
+
("Wall clock ms", "wallClockMs")):
|
|
319
|
+
rows.append(line(label, totals.get(key)))
|
|
320
|
+
for prefix, key in (("Unavailable", "unavailable"), ("Warning", "warnings")):
|
|
321
|
+
values = payload.get(key) if isinstance(payload.get(key), dict) else {}
|
|
322
|
+
for index, (name, count) in enumerate(sorted(values.items()), 1):
|
|
323
|
+
rows.append(line(f"{prefix} {index} reason", name))
|
|
324
|
+
rows.append(line(f"{prefix} {index} count", count))
|
|
325
|
+
models = payload.get("unmatchedModels") if isinstance(payload.get("unmatchedModels"), list) else []
|
|
326
|
+
for index, model in enumerate(models, 1):
|
|
327
|
+
rows.append(line(f"Unmatched model {index} name", model))
|
|
328
|
+
if not payload.get("ok"):
|
|
329
|
+
rows.append(line("Failure stage", payload.get("stage")))
|
|
330
|
+
rows.append(line("Failure reason", payload.get("reason")))
|
|
331
|
+
return "".join(rows)
|
|
332
|
+
|
|
333
|
+
|
|
280
334
|
if __name__ == "__main__":
|
|
281
335
|
raise SystemExit(main(sys.argv[1:]))
|