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
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
"""list/format/show."""
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
|
|
4
|
-
import json
|
|
5
4
|
import re as _re
|
|
6
5
|
from datetime import datetime, timedelta, timezone
|
|
7
6
|
from pathlib import Path
|
|
8
7
|
from typing import List, Optional
|
|
9
8
|
|
|
10
9
|
from .paths import resolve_under_root
|
|
10
|
+
from .json_boundary import JsonBoundaryError, load_owned_object
|
|
11
11
|
from .reconcile import _parse_iso
|
|
12
12
|
from .run_index_row import read_run_index
|
|
13
13
|
|
|
@@ -24,8 +24,8 @@ def list_projects(home: Path, *, only_active: bool = False,
|
|
|
24
24
|
if not meta_file.is_file():
|
|
25
25
|
continue
|
|
26
26
|
try:
|
|
27
|
-
meta =
|
|
28
|
-
except
|
|
27
|
+
meta = load_owned_object(meta_file, artifact="project metadata")
|
|
28
|
+
except JsonBoundaryError:
|
|
29
29
|
# 동시 쓰기/이전 크래시로 깨진 meta.json 하나가 전체 listing 을
|
|
30
30
|
# 죽이지 않도록 해당 프로젝트만 건너뛴다(다른 JSON 리더와 동일 정책).
|
|
31
31
|
continue
|
|
@@ -13,12 +13,18 @@ import json
|
|
|
13
13
|
import sys
|
|
14
14
|
from pathlib import Path
|
|
15
15
|
|
|
16
|
+
from .fixed_text import line
|
|
17
|
+
|
|
16
18
|
from okstra_project import resolve_project_root
|
|
19
|
+
from okstra_ctl.json_boundary import load_owned_object
|
|
17
20
|
|
|
18
21
|
|
|
19
22
|
def _project_id(project_root: Path) -> str:
|
|
20
23
|
try:
|
|
21
|
-
return
|
|
24
|
+
return load_owned_object(
|
|
25
|
+
project_root / ".okstra" / "project.json",
|
|
26
|
+
artifact="project configuration",
|
|
27
|
+
).get("projectId", "")
|
|
22
28
|
except (OSError, ValueError):
|
|
23
29
|
return ""
|
|
24
30
|
|
|
@@ -120,6 +126,20 @@ def scan_logs(logs_root: Path, project_root: Path, top: int = 20) -> dict:
|
|
|
120
126
|
}
|
|
121
127
|
|
|
122
128
|
|
|
129
|
+
def render_result_text(result: dict) -> str:
|
|
130
|
+
parts = ["# Okstra Log Inventory\n\n", line("Project root", result.get("projectRoot")), line("Logs root", result.get("logsRoot"))]
|
|
131
|
+
totals = result.get("totals") if isinstance(result.get("totals"), dict) else {}
|
|
132
|
+
for key, label in (("fileCount", "File count"), ("totalBytes", "Total bytes"), ("taskCount", "Task count"), ("promptBytes", "Prompt bytes"), ("transcriptBytes", "Transcript bytes"), ("pairedFileCount", "Paired file count")):
|
|
133
|
+
parts.append(line(label, totals.get(key)))
|
|
134
|
+
for row in result.get("topLargest", []):
|
|
135
|
+
if isinstance(row, dict):
|
|
136
|
+
parts.extend(("\n## Log\n\n", line("Task key", row.get("taskKey")), line("Phase", row.get("phase")), line("Worker", row.get("worker")), line("Sequence", row.get("seq")), line("Size bytes", row.get("sizeBytes")), line("Modified epoch", row.get("mtimeEpoch")), line("Path", row.get("path"))))
|
|
137
|
+
for row in result.get("perTask", []):
|
|
138
|
+
if isinstance(row, dict):
|
|
139
|
+
parts.extend(("\n## Task total\n\n", line("Task key", row.get("taskKey")), line("File count", row.get("fileCount")), line("Total bytes", row.get("totalBytes")), line("Oldest epoch", row.get("oldestEpoch")), line("Newest epoch", row.get("newestEpoch"))))
|
|
140
|
+
return "".join(parts)
|
|
141
|
+
|
|
142
|
+
|
|
123
143
|
def main(argv: list[str] | None = None) -> int:
|
|
124
144
|
parser = argparse.ArgumentParser(
|
|
125
145
|
prog="okstra log-report",
|
|
@@ -128,12 +148,14 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
128
148
|
parser.add_argument("--cwd", default=".")
|
|
129
149
|
parser.add_argument("--top", type=int, default=20, help="topLargest entry count")
|
|
130
150
|
parser.add_argument("--json", action="store_true", help="emit JSON (always on)")
|
|
151
|
+
parser.add_argument("--text", action="store_true", help="emit fixed text fields")
|
|
131
152
|
args = parser.parse_args(argv)
|
|
132
153
|
|
|
133
154
|
project_root = resolve_project_root(explicit_root=args.project_root, cwd=args.cwd)
|
|
134
155
|
result = {"ok": True, "projectRoot": str(project_root)}
|
|
135
156
|
result.update(scan_logs(project_root / ".okstra" / "tasks", project_root, top=args.top))
|
|
136
|
-
|
|
157
|
+
output = render_result_text(result) if args.text else json.dumps(result, ensure_ascii=False, indent=2)
|
|
158
|
+
print(output, end="" if args.text else "\n")
|
|
137
159
|
return 0
|
|
138
160
|
|
|
139
161
|
|
|
@@ -10,6 +10,7 @@ from pathlib import Path
|
|
|
10
10
|
from okstra_project.dirs import okstra_home
|
|
11
11
|
|
|
12
12
|
from .manager_launch import build_launch_packet
|
|
13
|
+
from .fixed_text import line, value_lines
|
|
13
14
|
from .manager_store import (
|
|
14
15
|
ManagerError,
|
|
15
16
|
append_directive,
|
|
@@ -23,11 +24,90 @@ from .manager_store import (
|
|
|
23
24
|
from .manager_sync import status_task, sync_task
|
|
24
25
|
|
|
25
26
|
|
|
26
|
-
def
|
|
27
|
+
def render_manager_text(command: str, payload: dict | list[dict]) -> str:
|
|
28
|
+
"""manager 모델 표면에 승인된 식별자와 상태만 투영한다."""
|
|
29
|
+
allowed = {"init", "discover-projects", "new-project", "new-task-group", "new-task",
|
|
30
|
+
"task-assign", "task-note", "task-sync", "task-status", "task-run"}
|
|
31
|
+
if command not in allowed:
|
|
32
|
+
raise ValueError("unknown manager text purpose")
|
|
33
|
+
rows = [f"Okstra manager {command}\n", line("Status", "ready")]
|
|
34
|
+
if isinstance(payload, list):
|
|
35
|
+
rows.append(line("Result count", len(payload)))
|
|
36
|
+
rows.extend(_render_manager_projects(payload))
|
|
37
|
+
return "".join(rows)
|
|
38
|
+
if command == "task-status":
|
|
39
|
+
rows.extend(_render_manager_status(payload))
|
|
40
|
+
rows.extend(value_lines("Status details", payload))
|
|
41
|
+
return "".join(rows)
|
|
42
|
+
for label, key in (("Manager ID", "managerId"), ("Schema version", "schemaVersion"),
|
|
43
|
+
("Task key", "taskKey"), ("Project ID", "projectId"),
|
|
44
|
+
("Task group", "taskGroup"), ("Task ID", "taskId"),
|
|
45
|
+
("Work status", "workStatus"), ("Run command", "command"),
|
|
46
|
+
("Backend", "backend"),
|
|
47
|
+
("Worker dispatch backend", "workerDispatchBackend"),
|
|
48
|
+
("Project root", "projectRoot"), ("Context path", "contextPath")):
|
|
49
|
+
if key in payload:
|
|
50
|
+
rows.append(line(label, payload.get(key)))
|
|
51
|
+
run_args = payload.get("runArgs")
|
|
52
|
+
if isinstance(run_args, list):
|
|
53
|
+
for index, value in enumerate(run_args, 1):
|
|
54
|
+
rows.append(line(f"Run arg {index}", value))
|
|
55
|
+
structures = {
|
|
56
|
+
"new-project": (("Projects", "projects"),),
|
|
57
|
+
"new-task": (("Manifest", "manifest"), ("Children", "children")),
|
|
58
|
+
"task-assign": (("Children", "children"),),
|
|
59
|
+
"task-sync": (("Children", "children"),),
|
|
60
|
+
}
|
|
61
|
+
for label, key in structures.get(command, ()):
|
|
62
|
+
rows.extend(value_lines(label, payload.get(key)))
|
|
63
|
+
if command == "task-note":
|
|
64
|
+
for label, key in (("Scope", "scope"), ("Directive project ID", "projectId"),
|
|
65
|
+
("Directive body", "body"), ("Created at", "createdAt"),
|
|
66
|
+
("Source", "source")):
|
|
67
|
+
rows.append(line(label, payload.get(key)))
|
|
68
|
+
if command == "task-sync" and "syncedAt" in payload:
|
|
69
|
+
rows.append(line("Synced at", payload.get("syncedAt")))
|
|
70
|
+
return "".join(rows)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _render_manager_projects(projects: list[dict]) -> list[str]:
|
|
74
|
+
rows: list[str] = []
|
|
75
|
+
for index, project in enumerate(projects, 1):
|
|
76
|
+
if not isinstance(project, dict):
|
|
77
|
+
continue
|
|
78
|
+
for label, key in (("ID", "projectId"), ("root", "projectRoot"),
|
|
79
|
+
("run count", "runCount"), ("active count", "activeCount"),
|
|
80
|
+
("last run at", "lastRunAt")):
|
|
81
|
+
rows.append(line(f"Project {index} {label}", project.get(key)))
|
|
82
|
+
return rows
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _render_manager_status(payload: dict) -> list[str]:
|
|
86
|
+
rows: list[str] = []
|
|
87
|
+
manifest = payload.get("manifest") if isinstance(payload.get("manifest"), dict) else {}
|
|
88
|
+
for label, key in (("Manager ID", "managerId"), ("Task group", "taskGroup"),
|
|
89
|
+
("Task ID", "taskId"), ("Progress mode", "progressMode")):
|
|
90
|
+
rows.append(line(label, manifest.get(key)))
|
|
91
|
+
summary = payload.get("summary") if isinstance(payload.get("summary"), dict) else {}
|
|
92
|
+
for key in ("planned", "missing", "running", "done", "blocked"):
|
|
93
|
+
rows.append(line(f"Summary {key}", summary.get(key)))
|
|
94
|
+
children = payload.get("children") if isinstance(payload.get("children"), list) else []
|
|
95
|
+
for index, child in enumerate(children, 1):
|
|
96
|
+
if not isinstance(child, dict):
|
|
97
|
+
continue
|
|
98
|
+
for label, key in (("task key", "taskKey"), ("run status", "latestRunStatus"),
|
|
99
|
+
("current phase", "currentPhase"), ("work status", "workStatus"),
|
|
100
|
+
("report path", "latestReportRecordPath"), ("error", "error")):
|
|
101
|
+
if key in child:
|
|
102
|
+
rows.append(line(f"Child {index} {label}", child.get(key)))
|
|
103
|
+
return rows
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _emit(command: str, payload: dict | list[dict], as_json: bool) -> None:
|
|
27
107
|
if as_json:
|
|
28
108
|
print(json.dumps(payload, ensure_ascii=False, indent=2))
|
|
29
109
|
return
|
|
30
|
-
print(
|
|
110
|
+
print(render_manager_text(command, payload), end="")
|
|
31
111
|
|
|
32
112
|
|
|
33
113
|
def _normalize_global_flags(argv: list[str] | None) -> list[str]:
|
|
@@ -142,11 +222,12 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
142
222
|
home = okstra_home()
|
|
143
223
|
try:
|
|
144
224
|
if args.command == "init":
|
|
145
|
-
_emit(init_manager(home, args.manager_id), args.json)
|
|
225
|
+
_emit("init", init_manager(home, args.manager_id), args.json)
|
|
146
226
|
elif args.command == "discover-projects":
|
|
147
|
-
_emit(discover_projects(home), args.json)
|
|
227
|
+
_emit("discover-projects", discover_projects(home), args.json)
|
|
148
228
|
elif args.command == "new" and args.new_command == "project":
|
|
149
229
|
_emit(
|
|
230
|
+
"new-project",
|
|
150
231
|
link_project(
|
|
151
232
|
home,
|
|
152
233
|
args.manager_id,
|
|
@@ -158,9 +239,10 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
158
239
|
args.json,
|
|
159
240
|
)
|
|
160
241
|
elif args.command == "new" and args.new_command == "task-group":
|
|
161
|
-
_emit(create_task_group(home, args.manager_id, args.task_group), args.json)
|
|
242
|
+
_emit("new-task-group", create_task_group(home, args.manager_id, args.task_group), args.json)
|
|
162
243
|
elif args.command == "new" and args.new_command == "task":
|
|
163
244
|
_emit(
|
|
245
|
+
"new-task",
|
|
164
246
|
create_task(
|
|
165
247
|
home,
|
|
166
248
|
args.manager_id,
|
|
@@ -175,6 +257,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
175
257
|
)
|
|
176
258
|
elif args.command == "task" and args.task_command == "assign":
|
|
177
259
|
_emit(
|
|
260
|
+
"task-assign",
|
|
178
261
|
assign_child(
|
|
179
262
|
home,
|
|
180
263
|
args.manager_id,
|
|
@@ -190,6 +273,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
190
273
|
)
|
|
191
274
|
elif args.command == "task" and args.task_command == "note":
|
|
192
275
|
_emit(
|
|
276
|
+
"task-note",
|
|
193
277
|
append_directive(
|
|
194
278
|
home,
|
|
195
279
|
args.manager_id,
|
|
@@ -202,11 +286,12 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
202
286
|
args.json,
|
|
203
287
|
)
|
|
204
288
|
elif args.command == "task" and args.task_command == "sync":
|
|
205
|
-
_emit(sync_task(home, args.manager_id, args.task_group, args.task_id), args.json)
|
|
289
|
+
_emit("task-sync", sync_task(home, args.manager_id, args.task_group, args.task_id), args.json)
|
|
206
290
|
elif args.command == "task" and args.task_command == "status":
|
|
207
|
-
_emit(status_task(home, args.manager_id, args.task_group, args.task_id), args.json)
|
|
291
|
+
_emit("task-status", status_task(home, args.manager_id, args.task_group, args.task_id), args.json)
|
|
208
292
|
elif args.command == "task" and args.task_command == "run":
|
|
209
293
|
_emit(
|
|
294
|
+
"task-run",
|
|
210
295
|
build_launch_packet(
|
|
211
296
|
home,
|
|
212
297
|
args.manager_id,
|
|
@@ -9,6 +9,11 @@ from pathlib import Path
|
|
|
9
9
|
from okstra_project import project_json_path, upsert_project_json
|
|
10
10
|
|
|
11
11
|
from okstra_ctl.jsonl import append_jsonl
|
|
12
|
+
from okstra_ctl.json_boundary import (
|
|
13
|
+
JsonBoundaryError,
|
|
14
|
+
load_owned_object,
|
|
15
|
+
write_owned_object_atomic,
|
|
16
|
+
)
|
|
12
17
|
|
|
13
18
|
from .manager_paths import (
|
|
14
19
|
ManagerPathError,
|
|
@@ -32,18 +37,15 @@ def _now_iso() -> str:
|
|
|
32
37
|
|
|
33
38
|
|
|
34
39
|
def _write_json(path: Path, payload: dict) -> None:
|
|
35
|
-
path
|
|
36
|
-
tmp = path.with_suffix(path.suffix + ".tmp")
|
|
37
|
-
tmp.write_text(json.dumps(payload, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
|
|
38
|
-
os.replace(tmp, path)
|
|
40
|
+
write_owned_object_atomic(path, payload, artifact="manager state")
|
|
39
41
|
|
|
40
42
|
|
|
41
43
|
def _read_json(path: Path) -> dict:
|
|
42
44
|
if not path.is_file():
|
|
43
45
|
raise ManagerError(f"manager file not found: {path}")
|
|
44
46
|
try:
|
|
45
|
-
data =
|
|
46
|
-
except
|
|
47
|
+
data = load_owned_object(path, artifact="manager state")
|
|
48
|
+
except JsonBoundaryError as exc:
|
|
47
49
|
raise ManagerError(f"failed to parse {path}: {exc}") from exc
|
|
48
50
|
if not isinstance(data, dict):
|
|
49
51
|
raise ManagerError(f"expected JSON object in {path}")
|
|
@@ -101,8 +103,8 @@ def discover_projects(home: Path) -> list[dict]:
|
|
|
101
103
|
items: list[dict] = []
|
|
102
104
|
for meta in sorted(base.glob("*/meta.json")):
|
|
103
105
|
try:
|
|
104
|
-
data =
|
|
105
|
-
except
|
|
106
|
+
data = load_owned_object(meta, artifact="project metadata")
|
|
107
|
+
except JsonBoundaryError:
|
|
106
108
|
continue
|
|
107
109
|
if isinstance(data, dict):
|
|
108
110
|
items.append(
|
|
@@ -134,8 +136,8 @@ def link_project(
|
|
|
134
136
|
project_json = project_json_path(root)
|
|
135
137
|
if project_json.is_file():
|
|
136
138
|
try:
|
|
137
|
-
existing =
|
|
138
|
-
except
|
|
139
|
+
existing = load_owned_object(project_json, artifact="project metadata")
|
|
140
|
+
except JsonBoundaryError as exc:
|
|
139
141
|
raise ManagerError(f"failed to parse {project_json}: {exc}") from exc
|
|
140
142
|
existing_id = str(existing.get("projectId") or "")
|
|
141
143
|
if existing_id != project_id:
|
|
@@ -11,6 +11,8 @@ import json
|
|
|
11
11
|
from pathlib import Path
|
|
12
12
|
from typing import Optional
|
|
13
13
|
|
|
14
|
+
from .json_boundary import load_owned_object
|
|
15
|
+
|
|
14
16
|
|
|
15
17
|
def build_analysis_material(brief_path: Path, directive: str = "") -> str:
|
|
16
18
|
"""`analysis-material.md` 본문 문자열을 돌려준다."""
|
|
@@ -35,7 +37,9 @@ def resolve_related_tasks(
|
|
|
35
37
|
existing: list[str] = []
|
|
36
38
|
if task_manifest_path and Path(task_manifest_path).exists():
|
|
37
39
|
try:
|
|
38
|
-
manifest =
|
|
40
|
+
manifest = load_owned_object(
|
|
41
|
+
Path(task_manifest_path), artifact="task manifest"
|
|
42
|
+
)
|
|
39
43
|
existing = manifest.get("relatedTasks") or []
|
|
40
44
|
if not isinstance(existing, list):
|
|
41
45
|
existing = []
|
|
@@ -24,6 +24,11 @@ from pathlib import Path
|
|
|
24
24
|
from typing import Optional
|
|
25
25
|
|
|
26
26
|
from okstra_ctl.worktree import is_git_work_tree
|
|
27
|
+
from okstra_ctl.json_boundary import (
|
|
28
|
+
JsonBoundaryError,
|
|
29
|
+
load_owned_object,
|
|
30
|
+
write_owned_object_atomic,
|
|
31
|
+
)
|
|
27
32
|
# 모듈 import 인 이유: 이 파일의 공개 함수들이 `okstra_home` 이라는 파라미터
|
|
28
33
|
# 이름을 쓰고 있어, 같은 이름의 함수를 직접 import 하면 가려진다.
|
|
29
34
|
from okstra_project import dirs as project_dirs
|
|
@@ -305,9 +310,10 @@ def _scan_registries(project_root: Path, *, okstra_home: Path) -> list[dict]:
|
|
|
305
310
|
registry = okstra_home / "worktrees" / "registry.json"
|
|
306
311
|
if registry.is_file():
|
|
307
312
|
try:
|
|
308
|
-
|
|
309
|
-
except
|
|
310
|
-
|
|
313
|
+
data = load_owned_object(registry, artifact="worktree registry")
|
|
314
|
+
except JsonBoundaryError:
|
|
315
|
+
data = {}
|
|
316
|
+
text = json.dumps(data, ensure_ascii=False)
|
|
311
317
|
if LEGACY_OKSTRA_DIR_NAME in text and _mentions_project_path(text, project_str):
|
|
312
318
|
updates.append({"file": str(registry), "rowCount": text.count(LEGACY_OKSTRA_DIR_NAME), "scope": "worktrees-registry"})
|
|
313
319
|
return updates
|
|
@@ -342,47 +348,45 @@ def _apply_registry_updates(plan: MigrationPlan, *, okstra_home: Path) -> int:
|
|
|
342
348
|
registry = okstra_home / "worktrees" / "registry.json"
|
|
343
349
|
if registry.is_file():
|
|
344
350
|
try:
|
|
345
|
-
|
|
346
|
-
except
|
|
347
|
-
|
|
351
|
+
data = load_owned_object(registry, artifact="worktree registry")
|
|
352
|
+
except JsonBoundaryError:
|
|
353
|
+
data = {}
|
|
348
354
|
# Only mutate when this project's root appears anywhere in the file —
|
|
349
355
|
# otherwise leave it alone (covers the case where registry.json
|
|
350
356
|
# contains an unrelated `.project-docs/okstra` literal in someone
|
|
351
357
|
# else's row).
|
|
358
|
+
text = json.dumps(data, ensure_ascii=False)
|
|
352
359
|
if _mentions_project_path(text, project_str) and LEGACY_OKSTRA_DIR_NAME in text:
|
|
353
|
-
|
|
354
|
-
if
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
LEGACY_OKSTRA_DIR_NAME
|
|
360
|
+
updated, changed = _replace_in_project_rows(data, project_str)
|
|
361
|
+
if changed:
|
|
362
|
+
write_owned_object_atomic(
|
|
363
|
+
registry, updated, artifact="worktree registry"
|
|
358
364
|
)
|
|
365
|
+
total += changed
|
|
359
366
|
return total
|
|
360
367
|
|
|
361
368
|
|
|
362
|
-
def _replace_in_project_rows(
|
|
369
|
+
def _replace_in_project_rows(
|
|
370
|
+
data: dict[str, object], project_str: str
|
|
371
|
+
) -> tuple[dict[str, object], int]:
|
|
363
372
|
"""For registry.json: replace `.project-docs/okstra` with `.okstra` only
|
|
364
373
|
inside rows that mention `project_str`. registry.json is structured JSON
|
|
365
374
|
(top-level object with task-key → row), so we operate at the JSON level
|
|
366
375
|
to avoid clobbering unrelated rows.
|
|
367
376
|
"""
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
except json.JSONDecodeError:
|
|
371
|
-
return text
|
|
372
|
-
if not isinstance(data, dict):
|
|
373
|
-
return text
|
|
374
|
-
changed = False
|
|
377
|
+
updated = dict(data)
|
|
378
|
+
changed = 0
|
|
375
379
|
for key, row in data.items():
|
|
376
|
-
row_text = json.dumps(row)
|
|
380
|
+
row_text = json.dumps(row, ensure_ascii=False)
|
|
377
381
|
if not _mentions_project_path(row_text, project_str) or LEGACY_OKSTRA_DIR_NAME not in row_text:
|
|
378
382
|
continue
|
|
379
383
|
new_row_text = row_text.replace(LEGACY_OKSTRA_DIR_NAME, OKSTRA_DIR_NAME)
|
|
380
384
|
if new_row_text != row_text:
|
|
381
|
-
|
|
382
|
-
changed
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
return
|
|
385
|
+
updated[key] = json.loads(new_row_text)
|
|
386
|
+
changed += row_text.count(LEGACY_OKSTRA_DIR_NAME) - new_row_text.count(
|
|
387
|
+
LEGACY_OKSTRA_DIR_NAME
|
|
388
|
+
)
|
|
389
|
+
return updated, changed
|
|
386
390
|
|
|
387
391
|
|
|
388
392
|
def main(argv: Optional[list[str]] = None) -> int:
|
|
@@ -17,6 +17,7 @@ from .model_pool import ModelPool, ModelPoolValidationError
|
|
|
17
17
|
from .ports.host_model import HostModelBindingError, HostModelBindingRequest
|
|
18
18
|
from .registry.host_registry import default_host_registry
|
|
19
19
|
from .registry.provider_registry import default_provider_registry
|
|
20
|
+
from .json_boundary import load_owned_object, write_owned_object_atomic
|
|
20
21
|
|
|
21
22
|
|
|
22
23
|
class ModelCliError(ValueError):
|
|
@@ -226,24 +227,11 @@ def _config_path(scope: str, cwd: Path) -> Path:
|
|
|
226
227
|
def _read_json(path: Path) -> dict[str, Any]:
|
|
227
228
|
if not path.is_file():
|
|
228
229
|
return {}
|
|
229
|
-
|
|
230
|
-
return payload if isinstance(payload, dict) else {}
|
|
230
|
+
return load_owned_object(path, artifact="model configuration")
|
|
231
231
|
|
|
232
232
|
|
|
233
233
|
def _write_json_atomic(path: Path, payload: dict[str, Any]) -> None:
|
|
234
|
-
path
|
|
235
|
-
handle = tempfile.NamedTemporaryFile(
|
|
236
|
-
"w", encoding="utf-8", delete=False, dir=path.parent
|
|
237
|
-
)
|
|
238
|
-
try:
|
|
239
|
-
json.dump(payload, handle, ensure_ascii=False, indent=2)
|
|
240
|
-
handle.write("\n")
|
|
241
|
-
handle.close()
|
|
242
|
-
os.replace(handle.name, path)
|
|
243
|
-
except Exception:
|
|
244
|
-
handle.close()
|
|
245
|
-
Path(handle.name).unlink(missing_ok=True)
|
|
246
|
-
raise
|
|
234
|
+
write_owned_object_atomic(path, payload, artifact="model configuration")
|
|
247
235
|
|
|
248
236
|
|
|
249
237
|
def main(argv: list[str] | None = None) -> int:
|