okstra 0.186.7 → 0.188.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/cli-registry.d.mts +88 -1
- package/dist/cli-registry.mjs +68 -111
- package/dist/cli-registry.mjs.map +1 -1
- package/dist/commands/execute/render-bundle.mjs +0 -1
- package/dist/commands/execute/render-bundle.mjs.map +1 -1
- package/dist/commands/execute/run.mjs +8 -3
- package/dist/commands/execute/run.mjs.map +1 -1
- package/dist/commands/lifecycle/check-project.mjs +1 -14
- package/dist/commands/lifecycle/check-project.mjs.map +1 -1
- package/dist/commands/lifecycle/config.mjs +38 -40
- package/dist/commands/lifecycle/config.mjs.map +1 -1
- package/dist/commands/lifecycle/doctor.d.mts +22 -7
- package/dist/commands/lifecycle/doctor.mjs +77 -49
- package/dist/commands/lifecycle/doctor.mjs.map +1 -1
- package/dist/commands/lifecycle/install.d.mts +12 -10
- package/dist/commands/lifecycle/install.mjs +107 -40
- package/dist/commands/lifecycle/install.mjs.map +1 -1
- package/dist/commands/lifecycle/paths.mjs +8 -3
- package/dist/commands/lifecycle/paths.mjs.map +1 -1
- package/dist/commands/lifecycle/preflight.mjs +2 -1
- package/dist/commands/lifecycle/preflight.mjs.map +1 -1
- package/dist/commands/lifecycle/setup.mjs +22 -36
- package/dist/commands/lifecycle/setup.mjs.map +1 -1
- package/dist/commands/lifecycle/uninstall.d.mts +4 -2
- package/dist/commands/lifecycle/uninstall.mjs +59 -15
- package/dist/commands/lifecycle/uninstall.mjs.map +1 -1
- package/dist/commands/memory/memory.mjs +7 -1
- package/dist/commands/memory/memory.mjs.map +1 -1
- package/dist/lib/helper-scripts.d.mts +1 -1
- package/dist/lib/helper-scripts.mjs +10 -18
- package/dist/lib/helper-scripts.mjs.map +1 -1
- package/dist/lib/host-config.d.mts +72 -0
- package/dist/lib/host-config.mjs +404 -0
- package/dist/lib/host-config.mjs.map +1 -0
- package/dist/lib/host-registry-client.d.mts +2 -2
- package/dist/lib/host-registry-client.mjs +0 -3
- package/dist/lib/host-registry-client.mjs.map +1 -1
- package/dist/lib/install-assets.d.mts +1 -0
- package/dist/lib/install-assets.mjs +4 -0
- package/dist/lib/install-assets.mjs.map +1 -1
- package/dist/lib/proc.d.mts +2 -0
- package/dist/lib/proc.mjs +12 -0
- package/dist/lib/proc.mjs.map +1 -1
- package/dist/lib/python-command.d.mts +2 -0
- package/dist/lib/python-command.mjs +52 -0
- package/dist/lib/python-command.mjs.map +1 -0
- package/dist/lib/python-helper.d.mts +2 -5
- package/dist/lib/python-helper.mjs +3 -52
- package/dist/lib/python-helper.mjs.map +1 -1
- package/dist/lib/runtime-payload.d.mts +23 -0
- package/dist/lib/runtime-payload.mjs +57 -0
- package/dist/lib/runtime-payload.mjs.map +1 -0
- package/dist/lib/types.d.mts +24 -13
- package/docs/architecture/storage-model.md +21 -5
- package/docs/architecture.md +39 -38
- package/docs/cli.md +55 -76
- package/docs/coding-rules.md +295 -0
- package/docs/container.md +10 -36
- package/docs/contributor-change-matrix.md +1 -1
- package/docs/for-ai/skills/okstra-code-review.md +0 -1
- package/docs/for-ai/skills/okstra-container-build.md +8 -41
- package/docs/for-ai/skills/okstra-inspect.md +4 -4
- package/docs/for-ai/skills/okstra-manager.md +2 -2
- package/docs/for-ai/skills/okstra-rollup.md +0 -1
- package/docs/for-ai/skills/okstra-run.md +3 -3
- package/docs/for-ai/skills/okstra-user-response.md +0 -1
- package/docs/performance-improvement-plan-v2.md +1 -1
- package/docs/project-structure-overview.md +62 -62
- package/docs/task-process/README.md +3 -3
- package/docs/task-process/common-flow.md +1 -1
- package/docs/task-process/error-analysis.md +3 -3
- package/docs/task-process/implementation-planning.md +3 -1
- package/docs/task-process/release-handoff.md +1 -1
- package/package.json +3 -2
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/workers/claude-worker.md +6 -11
- package/runtime/agents/workers/report-writer-worker.md +1 -1
- package/runtime/bin/lib/okstra/cli.sh +4 -0
- package/runtime/bin/lib/okstra/globals.sh +2 -0
- package/runtime/bin/lib/okstra/interactive.sh +26 -173
- package/runtime/bin/lib/okstra/project-resolver.sh +23 -61
- package/runtime/bin/lib/okstra/usage.sh +3 -3
- package/runtime/bin/okstra-compact-reminder.sh +1 -5
- package/runtime/bin/okstra-error-log.py +19 -2
- package/runtime/bin/okstra-import-check.py +26 -0
- package/runtime/bin/okstra-inject-report-index.py +5 -4
- package/runtime/bin/okstra-provider-exec.py +22 -17
- package/runtime/bin/okstra-render-final-report.py +16 -6
- package/runtime/bin/okstra-render-report-views.py +47 -22
- package/runtime/bin/okstra-report-translate.py +30 -18
- package/runtime/bin/okstra-spawn-followups.py +4 -0
- package/runtime/bin/okstra-token-usage.py +3 -0
- package/runtime/bin/okstra.sh +4 -2
- package/runtime/bin/okstra_bootstrap.py +58 -0
- package/runtime/prompts/coding-preflight/overview.md +1 -1
- package/runtime/prompts/duties/planning-worker.md +1 -1
- package/runtime/prompts/launch.template.md +35 -9
- package/runtime/prompts/lead/adapters/cmux.md +3 -3
- package/runtime/prompts/lead/convergence.md +99 -88
- package/runtime/prompts/lead/okstra-lead-contract.md +63 -12
- package/runtime/prompts/lead/plan-body-verification.md +65 -46
- package/runtime/prompts/lead/report-writer.md +35 -5
- package/runtime/prompts/lead/team-contract.md +4 -1
- package/runtime/prompts/profiles/_clarification-recommendation.md +2 -1
- package/runtime/prompts/profiles/_coding-conventions-preflight.md +1 -1
- package/runtime/prompts/profiles/_common-contract.md +3 -3
- package/runtime/prompts/profiles/_coverage-critic.md +1 -1
- package/runtime/prompts/profiles/_implementation-deliverable.md +2 -2
- package/runtime/prompts/profiles/_implementation-executor.md +3 -1
- package/runtime/prompts/profiles/_implementation-verifier.md +16 -2
- package/runtime/prompts/profiles/error-analysis.md +4 -3
- package/runtime/prompts/profiles/final-verification.md +2 -2
- package/runtime/prompts/profiles/implementation-planning.md +22 -17
- package/runtime/prompts/profiles/implementation.md +3 -2
- package/runtime/prompts/profiles/requirements-discovery.md +2 -1
- package/runtime/prompts/wizard/prompts.ko.json +29 -8
- package/runtime/python/okstra_ctl/__init__.py +6 -27
- package/runtime/python/okstra_ctl/adapters/hosts/claude-code/relay.md +4 -5
- package/runtime/python/okstra_ctl/adapters/hosts/codex/adapter.py +21 -2
- package/runtime/python/okstra_ctl/adapters/hosts/external/adapter.py +3 -12
- package/runtime/python/okstra_ctl/adapters/providers/antigravity/adapter.py +63 -22
- package/runtime/python/okstra_ctl/adapters/providers/claude/adapter.py +20 -21
- package/runtime/python/okstra_ctl/adapters/providers/codex/adapter.py +37 -20
- package/runtime/python/okstra_ctl/adapters/providers/grok/adapter.py +86 -14
- package/runtime/python/okstra_ctl/adapters/providers/kimi/adapter.py +12 -12
- package/runtime/python/okstra_ctl/adapters/runtime/cmux.py +4 -7
- package/runtime/python/okstra_ctl/agent/__init__.py +1 -0
- package/runtime/python/okstra_ctl/{agent_activity.py → agent/activity.py} +16 -2
- package/runtime/python/okstra_ctl/{agent_invocation.py → agent/invocation.py} +34 -5
- package/runtime/python/okstra_ctl/agent/prompt_cli/__init__.py +19 -0
- package/runtime/python/okstra_ctl/agent/prompt_cli/__main__.py +11 -0
- package/runtime/python/okstra_ctl/agent/prompt_cli/cli.py +228 -0
- package/runtime/python/okstra_ctl/agent/prompt_cli/dynamic_verifier.py +155 -0
- package/runtime/python/okstra_ctl/agent/prompt_cli/emit.py +66 -0
- package/runtime/python/okstra_ctl/agent/prompt_cli/inputs.py +138 -0
- package/runtime/python/okstra_ctl/agent/prompt_cli/materialize.py +605 -0
- package/runtime/python/okstra_ctl/agent/prompt_cli/results.py +197 -0
- package/runtime/python/okstra_ctl/agent/prompt_cli/run_identity.py +107 -0
- package/runtime/python/okstra_ctl/analysis_packet.py +47 -4
- package/runtime/python/okstra_ctl/approval_decisions.py +287 -10
- package/runtime/python/okstra_ctl/assignment_resolver.py +0 -12
- package/runtime/python/okstra_ctl/attempt_evidence.py +12 -24
- package/runtime/python/okstra_ctl/blocking_checks.py +251 -0
- package/runtime/python/okstra_ctl/brief_frontmatter.py +0 -7
- package/runtime/python/okstra_ctl/clarification_items/__init__.py +102 -0
- package/runtime/python/okstra_ctl/clarification_items/carry.py +224 -0
- package/runtime/python/okstra_ctl/clarification_items/dispositions.py +135 -0
- package/runtime/python/okstra_ctl/clarification_items/parsing.py +253 -0
- package/runtime/python/okstra_ctl/clarification_items/rows.py +115 -0
- package/runtime/python/okstra_ctl/clarification_items/scan.py +215 -0
- package/runtime/python/okstra_ctl/clarification_items/sidecars.py +211 -0
- package/runtime/python/okstra_ctl/cmux.py +38 -31
- package/runtime/python/okstra_ctl/code_review_target.py +163 -1
- package/runtime/python/okstra_ctl/conformance.py +23 -0
- package/runtime/python/okstra_ctl/container.py +38 -421
- package/runtime/python/okstra_ctl/context_cost.py +32 -4
- package/runtime/python/okstra_ctl/contract_graph_cli.py +7 -0
- package/runtime/python/okstra_ctl/convergence.py +107 -16
- package/runtime/python/okstra_ctl/convergence_critic_prompt.py +345 -0
- package/runtime/python/okstra_ctl/convergence_engine.py +382 -70
- package/runtime/python/okstra_ctl/convergence_store.py +5 -29
- package/runtime/python/okstra_ctl/design_prep.py +108 -12
- package/runtime/python/okstra_ctl/design_snapshot.py +11 -1
- package/runtime/python/okstra_ctl/design_surfaces.py +29 -2
- package/runtime/python/okstra_ctl/dispatch_core.py +236 -49
- package/runtime/python/okstra_ctl/dispatch_state.py +417 -41
- package/runtime/python/okstra_ctl/doctor_cli.py +48 -0
- package/runtime/python/okstra_ctl/domain/worker_exec.py +10 -5
- package/runtime/python/okstra_ctl/domain/worker_presentation.py +28 -1
- package/runtime/python/okstra_ctl/domain/worker_stream.py +52 -21
- package/runtime/python/okstra_ctl/domain/write_policy.py +219 -0
- package/runtime/python/okstra_ctl/entrypoints/hosts.py +9 -2
- package/runtime/python/okstra_ctl/error_log_core.py +1 -1
- package/runtime/python/okstra_ctl/error_report.py +14 -0
- package/runtime/python/okstra_ctl/error_zip.py +18 -2
- package/runtime/python/okstra_ctl/exact_coverage.py +5 -0
- package/runtime/python/okstra_ctl/execution_identity.py +100 -7
- package/runtime/python/okstra_ctl/execution_manifest.py +67 -20
- package/runtime/python/okstra_ctl/execution_mutation_audit.py +90 -8
- package/runtime/python/okstra_ctl/final_report_paths.py +17 -0
- package/runtime/python/okstra_ctl/final_report_schema.py +319 -17
- package/runtime/python/okstra_ctl/git_reconcile.py +22 -2
- package/runtime/python/okstra_ctl/handoff.py +24 -1
- package/runtime/python/okstra_ctl/ids.py +6 -16
- package/runtime/python/okstra_ctl/implementation_direction.py +142 -33
- package/runtime/python/okstra_ctl/implementation_options.py +32 -13
- package/runtime/python/okstra_ctl/implementation_outcome.py +5 -2
- package/runtime/python/okstra_ctl/improvement_lenses.py +0 -14
- package/runtime/python/okstra_ctl/incremental_carry.py +69 -8
- package/runtime/python/okstra_ctl/incremental_scope.py +173 -12
- package/runtime/python/okstra_ctl/index.py +2 -2
- package/runtime/python/okstra_ctl/initial_prompt_materialization.py +48 -8
- package/runtime/python/okstra_ctl/interactive_cli.py +223 -0
- package/runtime/python/okstra_ctl/json_boundary.py +10 -0
- package/runtime/python/okstra_ctl/listing.py +0 -91
- package/runtime/python/okstra_ctl/locks.py +5 -19
- package/runtime/python/okstra_ctl/log_report.py +14 -0
- package/runtime/python/okstra_ctl/manager_cli.py +22 -3
- package/runtime/python/okstra_ctl/manager_launch.py +4 -8
- package/runtime/python/okstra_ctl/material.py +2 -2
- package/runtime/python/okstra_ctl/migrate.py +23 -1
- package/runtime/python/okstra_ctl/model_cli.py +19 -5
- package/runtime/python/okstra_ctl/model_discovery.py +46 -47
- package/runtime/python/okstra_ctl/model_io/__init__.py +1 -0
- package/runtime/python/okstra_ctl/model_io/lines.py +163 -0
- package/runtime/python/okstra_ctl/model_io/references.py +370 -0
- package/runtime/python/okstra_ctl/model_io/renderers.py +498 -0
- package/runtime/python/okstra_ctl/model_io_cli.py +30 -945
- package/runtime/python/okstra_ctl/model_pool.py +10 -6
- package/runtime/python/okstra_ctl/models.py +8 -8
- package/runtime/python/okstra_ctl/mutation_recovery.py +5 -63
- package/runtime/python/okstra_ctl/next_phase.py +121 -69
- package/runtime/python/okstra_ctl/pane_reclaim.py +23 -0
- package/runtime/python/okstra_ctl/pane_title.py +11 -8
- package/runtime/python/okstra_ctl/path_hints.py +36 -11
- package/runtime/python/okstra_ctl/paths.py +76 -8
- package/runtime/python/okstra_ctl/plan_items.py +132 -16
- package/runtime/python/okstra_ctl/plan_items_cli.py +486 -36
- package/runtime/python/okstra_ctl/plan_run_root.py +1 -1
- package/runtime/python/okstra_ctl/plan_validate_cli.py +51 -0
- package/runtime/python/okstra_ctl/plan_verify_cli.py +80 -0
- package/runtime/python/okstra_ctl/prepare_error.py +20 -0
- package/runtime/python/okstra_ctl/prior_planning.py +157 -0
- package/runtime/python/okstra_ctl/profile_show.py +18 -1
- package/runtime/python/okstra_ctl/project_setup_cli.py +140 -0
- package/runtime/python/okstra_ctl/recap.py +22 -1
- package/runtime/python/okstra_ctl/reconcile.py +84 -0
- package/runtime/python/okstra_ctl/registry/host_registry.py +18 -1
- package/runtime/python/okstra_ctl/render.py +98 -32
- package/runtime/python/okstra_ctl/render_final_report.py +4 -8
- package/runtime/python/okstra_ctl/report_assembly.py +297 -7
- package/runtime/python/okstra_ctl/report_contract.py +30 -7
- package/runtime/python/okstra_ctl/report_finalize.py +213 -16
- package/runtime/python/okstra_ctl/report_html/common.py +2 -2
- package/runtime/python/okstra_ctl/report_html/render.py +8 -12
- package/runtime/python/okstra_ctl/report_language.py +8 -4
- package/runtime/python/okstra_ctl/report_narrative.py +220 -34
- package/runtime/python/okstra_ctl/report_projections.py +8 -2
- package/runtime/python/okstra_ctl/report_synthesis_packet.py +393 -1
- package/runtime/python/okstra_ctl/report_translation.py +2 -35
- package/runtime/python/okstra_ctl/report_view_artifacts.py +42 -2
- package/runtime/python/okstra_ctl/report_views.py +4 -22
- package/runtime/python/okstra_ctl/resolve_task_key.py +13 -0
- package/runtime/python/okstra_ctl/rollup.py +13 -0
- package/runtime/python/okstra_ctl/run.py +639 -138
- package/runtime/python/okstra_ctl/run_audit.py +13 -0
- package/runtime/python/okstra_ctl/run_index_row.py +0 -12
- package/runtime/python/okstra_ctl/seeding.py +21 -11
- package/runtime/python/okstra_ctl/sequence.py +1 -1
- package/runtime/python/okstra_ctl/session.py +64 -14
- package/runtime/python/okstra_ctl/set_work_status.py +18 -0
- package/runtime/python/okstra_ctl/stage_integrate.py +15 -1
- package/runtime/python/okstra_ctl/stage_ledger.py +1 -1
- package/runtime/python/okstra_ctl/stage_map.py +57 -11
- package/runtime/python/okstra_ctl/stage_map_cli.py +83 -0
- package/runtime/python/okstra_ctl/stage_map_view.py +65 -0
- package/runtime/python/okstra_ctl/stage_targets.py +3 -12
- package/runtime/python/okstra_ctl/task_list_cli.py +138 -0
- package/runtime/python/okstra_ctl/task_show_cli.py +66 -0
- package/runtime/python/okstra_ctl/task_target.py +4 -16
- package/runtime/python/okstra_ctl/team.py +49 -2
- package/runtime/python/okstra_ctl/time_report.py +17 -2
- package/runtime/python/okstra_ctl/usage_report.py +11 -0
- package/runtime/python/okstra_ctl/user_response.py +55 -218
- package/runtime/python/okstra_ctl/user_response_values.py +242 -0
- package/runtime/python/okstra_ctl/validation_contract.py +3 -0
- package/runtime/python/okstra_ctl/verdict_blocks.py +28 -8
- package/runtime/python/okstra_ctl/verification_target.py +68 -0
- package/runtime/python/okstra_ctl/wizard.py +457 -92
- package/runtime/python/okstra_ctl/worker_artifacts.py +75 -16
- package/runtime/python/okstra_ctl/worker_audit_check.py +22 -0
- package/runtime/python/okstra_ctl/worker_dispatch.py +21 -0
- package/runtime/python/okstra_ctl/worker_liveness.py +33 -0
- package/runtime/python/okstra_ctl/worker_prompt_body.py +15 -2
- package/runtime/python/okstra_ctl/worker_prompt_contract.py +98 -7
- package/runtime/python/okstra_ctl/worker_prompt_headers.py +24 -2
- package/runtime/python/okstra_ctl/worker_prompt_policy.py +18 -3
- package/runtime/python/okstra_ctl/worker_request.py +2 -1
- package/runtime/python/okstra_ctl/worker_runner.py +58 -17
- package/runtime/python/okstra_ctl/worker_state.py +16 -1
- package/runtime/python/okstra_ctl/workflow.py +18 -1
- package/runtime/python/okstra_ctl/worktree/__init__.py +92 -0
- package/runtime/python/okstra_ctl/worktree/cleanliness.py +89 -0
- package/runtime/python/okstra_ctl/worktree/decisions.py +127 -0
- package/runtime/python/okstra_ctl/worktree/git_ops.py +165 -0
- package/runtime/python/okstra_ctl/worktree/linking.py +249 -0
- package/runtime/python/okstra_ctl/worktree/naming.py +91 -0
- package/runtime/python/okstra_ctl/worktree/provision.py +385 -0
- package/runtime/python/okstra_ctl/worktree/sync_config.py +181 -0
- package/runtime/python/okstra_ctl/worktree_cli.py +75 -0
- package/runtime/python/okstra_ctl/worktree_lookup_cli.py +39 -0
- package/runtime/python/okstra_ctl/worktree_registry.py +7 -0
- package/runtime/python/okstra_ctl/worktree_status_cli.py +53 -0
- package/runtime/python/okstra_ctl/write_policy.py +90 -213
- package/runtime/python/okstra_project/__init__.py +0 -4
- package/runtime/python/okstra_project/dirs.py +2 -2
- package/runtime/python/okstra_project/phase_pointer.py +84 -0
- package/runtime/python/okstra_project/slug.py +24 -0
- package/runtime/python/okstra_project/state.py +22 -230
- package/runtime/python/okstra_token_usage/antigravity.py +50 -12
- package/runtime/python/okstra_token_usage/claude.py +9 -1
- package/runtime/python/okstra_token_usage/cli.py +3 -1
- package/runtime/python/okstra_token_usage/collect.py +171 -36
- package/runtime/python/okstra_token_usage/cursor.py +2 -0
- package/runtime/python/okstra_token_usage/grok.py +1 -5
- package/runtime/python/okstra_token_usage/paths.py +26 -0
- package/runtime/python/okstra_token_usage/pricing.py +23 -8
- package/runtime/schemas/convergence-critic-results-v1.0.schema.json +5 -0
- package/runtime/schemas/execution-manifest-v2.schema.json +10 -10
- package/runtime/schemas/final-report-v2.0.schema.json +9 -26
- package/runtime/schemas/final-report-v3.0.schema.json +173 -41
- package/runtime/schemas/report-narrative-v3.0.schema.json +3 -2
- package/runtime/skills/okstra-brief-gen/SKILL.md +35 -2
- package/runtime/skills/okstra-container-build/SKILL.md +16 -47
- package/runtime/skills/okstra-inspect/facets/history.md +1 -1
- package/runtime/skills/okstra-inspect/facets/status.md +7 -6
- package/runtime/skills/okstra-pr-gen/SKILL.md +1 -1
- package/runtime/skills/okstra-run/SKILL.md +7 -5
- package/runtime/templates/report-writer-prompt-preamble.md +3 -1
- package/runtime/templates/reports/brief.template.md +6 -2
- package/runtime/templates/reports/error-analysis-input.template.md +2 -0
- package/runtime/templates/reports/final-verification-input.template.md +2 -0
- package/runtime/templates/reports/implementation-input.template.md +7 -1
- package/runtime/templates/reports/implementation-planning-input.template.md +4 -0
- package/runtime/templates/reports/quick-input.template.md +2 -0
- package/runtime/templates/reports/release-handoff-input.template.md +6 -1
- package/runtime/templates/reports/report.js +20 -3
- package/runtime/templates/reports/schedule.template.md +2 -0
- package/runtime/templates/reports/settings.template.json +0 -10
- package/runtime/templates/reports/task-brief.template.md +3 -0
- package/runtime/templates/reports/user-response.template.md +7 -3
- package/runtime/validators/checks/fixtures-01.py +57 -0
- package/runtime/validators/checks/fixtures-02.py +565 -0
- package/runtime/validators/checks/runners-01.py +108 -0
- package/runtime/validators/checks/validate-assets-01.py +60 -0
- package/runtime/validators/checks/validate-prompt-metadata-01.py +261 -0
- package/runtime/validators/checks/validate-tasks-01.py +46 -0
- package/runtime/validators/checks/validate-tasks-02.py +85 -0
- package/runtime/validators/checks/validate-tasks-03.py +61 -0
- package/runtime/validators/checks/validate-tasks-04.py +118 -0
- package/runtime/validators/forbidden_actions.py +73 -3
- package/runtime/validators/lib/common.sh +5 -0
- package/runtime/validators/lib/fixtures.sh +7 -591
- package/runtime/validators/lib/paths.sh +13 -0
- package/runtime/validators/lib/runners.sh +6 -104
- package/runtime/validators/lib/summary.sh +1 -1
- package/runtime/validators/lib/validate-assets.sh +6 -56
- package/runtime/validators/lib/validate-prompt-metadata.sh +6 -257
- package/runtime/validators/lib/validate-tasks.sh +9 -294
- package/runtime/validators/validate-implementation-plan-stages.py +4 -4
- package/runtime/validators/validate-run.py +1363 -2643
- package/runtime/validators/validate-workflow.sh +56 -16
- package/runtime/validators/validate_improvement_report.py +2 -1
- package/runtime/validators/validate_session_conformance.py +295 -49
- package/dist/commands/execute/agent-prompt.d.mts +0 -1
- package/dist/commands/execute/agent-prompt.mjs +0 -24
- package/dist/commands/execute/agent-prompt.mjs.map +0 -1
- package/dist/commands/execute/codex-dispatch.d.mts +0 -3
- package/dist/commands/execute/codex-dispatch.mjs +0 -6
- package/dist/commands/execute/codex-dispatch.mjs.map +0 -1
- package/dist/commands/execute/codex-run.d.mts +0 -3
- package/dist/commands/execute/codex-run.mjs +0 -62
- package/dist/commands/execute/codex-run.mjs.map +0 -1
- package/dist/commands/execute/convergence.d.mts +0 -1
- package/dist/commands/execute/convergence.mjs +0 -38
- package/dist/commands/execute/convergence.mjs.map +0 -1
- package/dist/commands/execute/error-log.d.mts +0 -1
- package/dist/commands/execute/error-log.mjs +0 -18
- package/dist/commands/execute/error-log.mjs.map +0 -1
- package/dist/commands/execute/git-reconcile.d.mts +0 -1
- package/dist/commands/execute/git-reconcile.mjs +0 -30
- package/dist/commands/execute/git-reconcile.mjs.map +0 -1
- package/dist/commands/execute/handoff.d.mts +0 -1
- package/dist/commands/execute/handoff.mjs +0 -31
- package/dist/commands/execute/handoff.mjs.map +0 -1
- package/dist/commands/execute/incremental-carry.d.mts +0 -1
- package/dist/commands/execute/incremental-carry.mjs +0 -20
- package/dist/commands/execute/incremental-carry.mjs.map +0 -1
- package/dist/commands/execute/incremental-scope.d.mts +0 -1
- package/dist/commands/execute/incremental-scope.mjs +0 -29
- package/dist/commands/execute/incremental-scope.mjs.map +0 -1
- package/dist/commands/execute/integrate-stages.d.mts +0 -1
- package/dist/commands/execute/integrate-stages.mjs +0 -24
- package/dist/commands/execute/integrate-stages.mjs.map +0 -1
- package/dist/commands/execute/pane-title.d.mts +0 -1
- package/dist/commands/execute/pane-title.mjs +0 -20
- package/dist/commands/execute/pane-title.mjs.map +0 -1
- package/dist/commands/execute/plan-items.d.mts +0 -1
- package/dist/commands/execute/plan-items.mjs +0 -9
- package/dist/commands/execute/plan-items.mjs.map +0 -1
- package/dist/commands/execute/plan-validate.d.mts +0 -1
- package/dist/commands/execute/plan-validate.mjs +0 -68
- package/dist/commands/execute/plan-validate.mjs.map +0 -1
- package/dist/commands/execute/plan-verify.d.mts +0 -1
- package/dist/commands/execute/plan-verify.mjs +0 -43
- package/dist/commands/execute/plan-verify.mjs.map +0 -1
- package/dist/commands/execute/spawn-followups.d.mts +0 -1
- package/dist/commands/execute/spawn-followups.mjs +0 -22
- package/dist/commands/execute/spawn-followups.mjs.map +0 -1
- package/dist/commands/execute/team.d.mts +0 -3
- package/dist/commands/execute/team.mjs +0 -66
- package/dist/commands/execute/team.mjs.map +0 -1
- package/dist/commands/execute/token-usage.d.mts +0 -1
- package/dist/commands/execute/token-usage.mjs +0 -19
- package/dist/commands/execute/token-usage.mjs.map +0 -1
- package/dist/commands/execute/worker-audit-check.d.mts +0 -1
- package/dist/commands/execute/worker-audit-check.mjs +0 -34
- package/dist/commands/execute/worker-audit-check.mjs.map +0 -1
- package/dist/commands/execute/worker-dispatch.d.mts +0 -7
- package/dist/commands/execute/worker-dispatch.mjs +0 -64
- package/dist/commands/execute/worker-dispatch.mjs.map +0 -1
- package/dist/commands/execute/worker-state.d.mts +0 -1
- package/dist/commands/execute/worker-state.mjs +0 -28
- package/dist/commands/execute/worker-state.mjs.map +0 -1
- package/dist/commands/execute/worktree-lookup.d.mts +0 -1
- package/dist/commands/execute/worktree-lookup.mjs +0 -92
- package/dist/commands/execute/worktree-lookup.mjs.map +0 -1
- package/dist/commands/execute/worktree-status.d.mts +0 -1
- package/dist/commands/execute/worktree-status.mjs +0 -121
- package/dist/commands/execute/worktree-status.mjs.map +0 -1
- package/dist/commands/inspect/code-review.d.mts +0 -1
- package/dist/commands/inspect/code-review.mjs +0 -32
- package/dist/commands/inspect/code-review.mjs.map +0 -1
- package/dist/commands/inspect/container.d.mts +0 -1
- package/dist/commands/inspect/container.mjs +0 -25
- package/dist/commands/inspect/container.mjs.map +0 -1
- package/dist/commands/inspect/context-cost.d.mts +0 -1
- package/dist/commands/inspect/context-cost.mjs +0 -25
- package/dist/commands/inspect/context-cost.mjs.map +0 -1
- package/dist/commands/inspect/design-prep.d.mts +0 -1
- package/dist/commands/inspect/design-prep.mjs +0 -22
- package/dist/commands/inspect/design-prep.mjs.map +0 -1
- package/dist/commands/inspect/error-report.d.mts +0 -1
- package/dist/commands/inspect/error-report.mjs +0 -25
- package/dist/commands/inspect/error-report.mjs.map +0 -1
- package/dist/commands/inspect/error-zip.d.mts +0 -1
- package/dist/commands/inspect/error-zip.mjs +0 -24
- package/dist/commands/inspect/error-zip.mjs.map +0 -1
- package/dist/commands/inspect/log-report.d.mts +0 -1
- package/dist/commands/inspect/log-report.mjs +0 -26
- package/dist/commands/inspect/log-report.mjs.map +0 -1
- package/dist/commands/inspect/model-io.d.mts +0 -1
- package/dist/commands/inspect/model-io.mjs +0 -25
- package/dist/commands/inspect/model-io.mjs.map +0 -1
- package/dist/commands/inspect/profile-show.d.mts +0 -1
- package/dist/commands/inspect/profile-show.mjs +0 -28
- package/dist/commands/inspect/profile-show.mjs.map +0 -1
- package/dist/commands/inspect/recap.d.mts +0 -1
- package/dist/commands/inspect/recap.mjs +0 -30
- package/dist/commands/inspect/recap.mjs.map +0 -1
- package/dist/commands/inspect/resolve-task-key.d.mts +0 -1
- package/dist/commands/inspect/resolve-task-key.mjs +0 -24
- package/dist/commands/inspect/resolve-task-key.mjs.map +0 -1
- package/dist/commands/inspect/rollup.d.mts +0 -1
- package/dist/commands/inspect/rollup.mjs +0 -25
- package/dist/commands/inspect/rollup.mjs.map +0 -1
- package/dist/commands/inspect/run-audit.d.mts +0 -1
- package/dist/commands/inspect/run-audit.mjs +0 -25
- package/dist/commands/inspect/run-audit.mjs.map +0 -1
- package/dist/commands/inspect/set-work-status.d.mts +0 -1
- package/dist/commands/inspect/set-work-status.mjs +0 -29
- package/dist/commands/inspect/set-work-status.mjs.map +0 -1
- package/dist/commands/inspect/stage-map.d.mts +0 -1
- package/dist/commands/inspect/stage-map.mjs +0 -131
- package/dist/commands/inspect/stage-map.mjs.map +0 -1
- package/dist/commands/inspect/task-list.d.mts +0 -1
- package/dist/commands/inspect/task-list.mjs +0 -149
- package/dist/commands/inspect/task-list.mjs.map +0 -1
- package/dist/commands/inspect/task-show.d.mts +0 -1
- package/dist/commands/inspect/task-show.mjs +0 -108
- package/dist/commands/inspect/task-show.mjs.map +0 -1
- package/dist/commands/inspect/time-report.d.mts +0 -1
- package/dist/commands/inspect/time-report.mjs +0 -24
- package/dist/commands/inspect/time-report.mjs.map +0 -1
- package/dist/commands/inspect/usage-report.d.mts +0 -1
- package/dist/commands/inspect/usage-report.mjs +0 -23
- package/dist/commands/inspect/usage-report.mjs.map +0 -1
- package/dist/commands/inspect/user-response.d.mts +0 -1
- package/dist/commands/inspect/user-response.mjs +0 -35
- package/dist/commands/inspect/user-response.mjs.map +0 -1
- package/dist/commands/inspect/worker-liveness.d.mts +0 -1
- package/dist/commands/inspect/worker-liveness.mjs +0 -45
- package/dist/commands/inspect/worker-liveness.mjs.map +0 -1
- package/dist/commands/lifecycle/contract-check.d.mts +0 -1
- package/dist/commands/lifecycle/contract-check.mjs +0 -18
- package/dist/commands/lifecycle/contract-check.mjs.map +0 -1
- package/dist/commands/lifecycle/migrate.d.mts +0 -1
- package/dist/commands/lifecycle/migrate.mjs +0 -30
- package/dist/commands/lifecycle/migrate.mjs.map +0 -1
- package/dist/commands/lifecycle/model.d.mts +0 -1
- package/dist/commands/lifecycle/model.mjs +0 -22
- package/dist/commands/lifecycle/model.mjs.map +0 -1
- package/dist/commands/manager.d.mts +0 -3
- package/dist/commands/manager.mjs +0 -50
- package/dist/commands/manager.mjs.map +0 -1
- package/dist/commands/report/agent-activity.d.mts +0 -1
- package/dist/commands/report/agent-activity.mjs +0 -20
- package/dist/commands/report/agent-activity.mjs.map +0 -1
- package/dist/commands/report/approval-decision.d.mts +0 -1
- package/dist/commands/report/approval-decision.mjs +0 -21
- package/dist/commands/report/approval-decision.mjs.map +0 -1
- package/dist/commands/report/design-snapshot.d.mts +0 -1
- package/dist/commands/report/design-snapshot.mjs +0 -19
- package/dist/commands/report/design-snapshot.mjs.map +0 -1
- package/dist/commands/report/finalize.d.mts +0 -4
- package/dist/commands/report/finalize.mjs +0 -64
- package/dist/commands/report/finalize.mjs.map +0 -1
- package/dist/commands/report/inject-report-index.d.mts +0 -1
- package/dist/commands/report/inject-report-index.mjs +0 -21
- package/dist/commands/report/inject-report-index.mjs.map +0 -1
- package/dist/commands/report/render-final-report.d.mts +0 -1
- package/dist/commands/report/render-final-report.mjs +0 -23
- package/dist/commands/report/render-final-report.mjs.map +0 -1
- package/dist/commands/report/render-views.d.mts +0 -1
- package/dist/commands/report/render-views.mjs +0 -27
- package/dist/commands/report/render-views.mjs.map +0 -1
- package/dist/commands/report/translate.d.mts +0 -1
- package/dist/commands/report/translate.mjs +0 -33
- package/dist/commands/report/translate.mjs.map +0 -1
- package/runtime/bin/lib/okstra/tmux-pane.sh +0 -40
- package/runtime/bin/lib/okstra-ctl/cmd-batch.sh +0 -59
- package/runtime/bin/lib/okstra-ctl/cmd-list.sh +0 -35
- package/runtime/bin/lib/okstra-ctl/cmd-open.sh +0 -36
- package/runtime/bin/lib/okstra-ctl/cmd-projects.sh +0 -26
- package/runtime/bin/lib/okstra-ctl/cmd-reconcile.sh +0 -29
- package/runtime/bin/lib/okstra-ctl/cmd-reindex.sh +0 -38
- package/runtime/bin/lib/okstra-ctl/cmd-rerun.sh +0 -345
- package/runtime/bin/lib/okstra-ctl/cmd-show.sh +0 -27
- package/runtime/bin/lib/okstra-ctl/cmd-tail.sh +0 -92
- package/runtime/bin/lib/okstra-ctl/main.sh +0 -41
- package/runtime/bin/lib/okstra-ctl/prepare.sh +0 -31
- package/runtime/bin/lib/okstra-ctl/usage.sh +0 -23
- package/runtime/bin/okstra-central.sh +0 -152
- package/runtime/bin/okstra-incremental-carry.py +0 -10
- package/runtime/bin/okstra-incremental-scope.py +0 -10
- package/runtime/bin/okstra-team-reconcile.sh +0 -36
- package/runtime/python/okstra_ctl/agent_prompt_cli.py +0 -1156
- package/runtime/python/okstra_ctl/batch.py +0 -60
- package/runtime/python/okstra_ctl/clarification_items.py +0 -1050
- package/runtime/python/okstra_ctl/container_registry.py +0 -72
- package/runtime/python/okstra_ctl/improvement_assignment.py +0 -61
- package/runtime/python/okstra_ctl/resolver.py +0 -54
- package/runtime/python/okstra_ctl/team_reconcile.py +0 -275
- package/runtime/python/okstra_ctl/tmux.py +0 -134
- package/runtime/python/okstra_ctl/worktree.py +0 -1099
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""`okstra doctor` 가 부르는 진단 프로젝션.
|
|
2
|
+
|
|
3
|
+
진단 로직 자체는 `okstra_ctl.doctor` 에 있다. 이 모듈은 그것을 CLI 경계로
|
|
4
|
+
노출하기만 한다 — 원래 `src/commands/lifecycle/doctor.mts` 의 문자열 리터럴
|
|
5
|
+
안에 있던 두 스니펫을 옮긴 것이다(docs/coding-rules.md R1 위반).
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import argparse
|
|
10
|
+
import json
|
|
11
|
+
|
|
12
|
+
from .doctor import model_pool_diagnostics, phase_diagnostics
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def main(argv: list[str] | None = None) -> int:
|
|
16
|
+
parser = argparse.ArgumentParser(prog="okstra doctor")
|
|
17
|
+
sub = parser.add_subparsers(dest="op", required=True)
|
|
18
|
+
|
|
19
|
+
pool = sub.add_parser("model-pool")
|
|
20
|
+
pool.add_argument("--host-runtime", default="claude-code")
|
|
21
|
+
pool.add_argument("--cwd", required=True)
|
|
22
|
+
|
|
23
|
+
phase = sub.add_parser("phase")
|
|
24
|
+
phase.add_argument("--phase", required=True)
|
|
25
|
+
phase.add_argument("--cwd", required=True)
|
|
26
|
+
phase.add_argument("--workspace-root", required=True)
|
|
27
|
+
# `--home` 은 사용자 홈이다. 에이전트 설치 루트가 `<home>/.claude/agents` 라서
|
|
28
|
+
# okstra 홈(~/.okstra)을 넘기면 모든 worker-agent 점검이 없는 경로를 짚는다.
|
|
29
|
+
phase.add_argument("--home", required=True)
|
|
30
|
+
phase.add_argument("--host-runtime", default="")
|
|
31
|
+
|
|
32
|
+
args = parser.parse_args(argv)
|
|
33
|
+
if args.op == "model-pool":
|
|
34
|
+
payload = model_pool_diagnostics(host_runtime=args.host_runtime, cwd=args.cwd)
|
|
35
|
+
else:
|
|
36
|
+
payload = phase_diagnostics(
|
|
37
|
+
args.phase,
|
|
38
|
+
cwd=args.cwd,
|
|
39
|
+
workspace_root=args.workspace_root,
|
|
40
|
+
home=args.home,
|
|
41
|
+
host_runtime=args.host_runtime,
|
|
42
|
+
)
|
|
43
|
+
print(json.dumps(payload, ensure_ascii=False))
|
|
44
|
+
return 0
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
if __name__ == "__main__":
|
|
48
|
+
raise SystemExit(main())
|
|
@@ -12,9 +12,12 @@ from pathlib import Path
|
|
|
12
12
|
from typing import Literal, Protocol, runtime_checkable
|
|
13
13
|
|
|
14
14
|
from .worker_presentation import Presentation
|
|
15
|
-
from
|
|
15
|
+
from .write_policy import WriteEnforcement, WritePolicy
|
|
16
16
|
|
|
17
17
|
SERVED_MODEL_MISMATCH_EXIT_CODE = 78
|
|
18
|
+
# JSON 이벤트 스트림이 result 이벤트 없이 깨끗하게(exit 0) 끝난 run.
|
|
19
|
+
# CLI 가 성공을 주장했지만 답을 내지 않았다 — sysexits 의 EX_PROTOCOL.
|
|
20
|
+
NO_RESULT_EXIT_CODE = 76
|
|
18
21
|
|
|
19
22
|
|
|
20
23
|
@dataclass(frozen=True)
|
|
@@ -22,10 +25,12 @@ class ExecutionPolicy:
|
|
|
22
25
|
"""How a non-interactive worker is allowed to act.
|
|
23
26
|
|
|
24
27
|
Identical for every worker: nobody is at the keyboard to answer an approval
|
|
25
|
-
prompt, so the gate has to be open
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
28
|
+
prompt, so the gate has to be open. No provider CLI enforces a sandbox
|
|
29
|
+
boundary any more, so `write_scope` is what the worker is told to write, not
|
|
30
|
+
what it is prevented from leaving — `write_policy` and `write_enforcement`
|
|
31
|
+
are what the run checks after the fact. Declared once here rather than per
|
|
32
|
+
provider, because a policy that lives in one adapter file per provider
|
|
33
|
+
drifts into one policy per provider.
|
|
29
34
|
"""
|
|
30
35
|
|
|
31
36
|
auto_approve: bool
|
|
@@ -30,6 +30,8 @@ Sink = Callable[[str], str | None]
|
|
|
30
30
|
Channel = Literal["stdout", "stderr"]
|
|
31
31
|
SinkSpec = tuple[Channel, Sink]
|
|
32
32
|
ObserveServedModel = Callable[[Mapping[str, Any]], str | None]
|
|
33
|
+
# 이벤트가 실어 온 토큰 사용량 스냅샷(공급자 어휘 그대로). 없으면 None.
|
|
34
|
+
ObserveUsage = Callable[[Mapping[str, Any]], Mapping[str, Any] | None]
|
|
33
35
|
|
|
34
36
|
WORKER = "worker"
|
|
35
37
|
|
|
@@ -113,15 +115,33 @@ class JsonEvents:
|
|
|
113
115
|
|
|
114
116
|
normalise: Normalise
|
|
115
117
|
observe: ObserveServedModel
|
|
118
|
+
# 어떤 CLI 는 토큰 사용량을 홈 디렉터리의 트랜스크립트가 아니라 이 스트림에만
|
|
119
|
+
# 싣는다(agy 의 `result.usage`). 어댑터가 그 스냅샷을 읽는 법을 넘기면
|
|
120
|
+
# 러너가 마지막 값을 status 사이드카에 적어, 수집기가 로그를 다시 파싱하지
|
|
121
|
+
# 않아도 된다. 어느 CLI 가 그런지는 이 계층이 알 필요도, 알아서도 안 된다.
|
|
122
|
+
observe_usage: ObserveUsage | None = None
|
|
116
123
|
# 어떤 CLI 의 streaming-json 은 문장이 아니라 토큰을 한 줄씩 보낸다. 붙이지
|
|
117
124
|
# 않으면 pane 이 한 글자 한 줄이 된다. 기본은 끄고, 그런 CLI 의 어댑터가 켠다 —
|
|
118
125
|
# 어느 CLI 가 그런지는 이 계층이 알 필요도, 알아서도 안 되는 사실이다.
|
|
119
126
|
join_adjacent_text: bool = False
|
|
127
|
+
# 이 어휘에서 "정상 종결" 을 뜻하는 원시 이벤트. 기본은 Result 이벤트지만,
|
|
128
|
+
# 어떤 CLI 의 어휘에는 Result 가 없고 별도의 종결 이벤트가 있다 — 어댑터가
|
|
129
|
+
# 자기 어휘의 표지를 넘긴다. 어느 CLI 가 그런지는 이 계층이 알 필요도,
|
|
130
|
+
# 알아서도 안 되는 사실이다. 판정 없이는 그런 CLI 의 건강한 run 전부가
|
|
131
|
+
# 결과 부재로 강등된다.
|
|
132
|
+
is_conclusion: Callable[[Mapping[str, Any]], bool] | None = None
|
|
120
133
|
_pending_text: list[str] = field(default_factory=list, repr=False, compare=False)
|
|
134
|
+
_saw_conclusion: list[bool] = field(
|
|
135
|
+
default_factory=list, repr=False, compare=False
|
|
136
|
+
)
|
|
121
137
|
|
|
122
138
|
def merges_stderr(self) -> bool:
|
|
123
139
|
return True
|
|
124
140
|
|
|
141
|
+
def concluded(self) -> bool:
|
|
142
|
+
"""스트림이 이 어휘의 종결 표지로 끝났는가."""
|
|
143
|
+
return bool(self._saw_conclusion)
|
|
144
|
+
|
|
125
145
|
def sinks(self, writer: TranscriptWriter) -> tuple[SinkSpec, ...]:
|
|
126
146
|
def emit(line: str) -> str | None:
|
|
127
147
|
stripped = line.strip()
|
|
@@ -138,6 +158,8 @@ class JsonEvents:
|
|
|
138
158
|
if not isinstance(event, dict):
|
|
139
159
|
return None
|
|
140
160
|
self.observe(event)
|
|
161
|
+
if self.is_conclusion is not None and self.is_conclusion(event):
|
|
162
|
+
self._saw_conclusion.append(True)
|
|
141
163
|
closing: str | None = None
|
|
142
164
|
for entry in self.normalise(event):
|
|
143
165
|
closing = self._write_entry(writer, entry) or closing
|
|
@@ -164,7 +186,12 @@ class JsonEvents:
|
|
|
164
186
|
screen=format_live(entry),
|
|
165
187
|
archive=format_log(entry),
|
|
166
188
|
)
|
|
167
|
-
|
|
189
|
+
closing = final_text(entry)
|
|
190
|
+
if closing is not None:
|
|
191
|
+
# Result 이벤트는 어휘와 무관한 종결이다. 빈 결과 텍스트도 종결이므로
|
|
192
|
+
# closing_text 의 진릿값과 따로 기록한다.
|
|
193
|
+
self._saw_conclusion.append(True)
|
|
194
|
+
return closing
|
|
168
195
|
|
|
169
196
|
def _publish_complete_lines(self, writer: TranscriptWriter) -> None:
|
|
170
197
|
combined = "".join(self._pending_text)
|
|
@@ -12,9 +12,10 @@ to be worth a default here — ``content_block_events``, whose events are keyed
|
|
|
12
12
|
provider, and an adapter that speaks something else says so in its own file.
|
|
13
13
|
|
|
14
14
|
Screen output folds what the log keeps in full — a tool call becomes one line, a
|
|
15
|
-
tool result becomes an outcome
|
|
16
|
-
|
|
17
|
-
rather than from log writes (see the
|
|
15
|
+
tool result becomes an outcome, a size, and the first few rows of what came
|
|
16
|
+
back. Thinking is dropped at normalisation, which is safe only because worker
|
|
17
|
+
liveness is measured from stream arrival rather than from log writes (see the
|
|
18
|
+
spec's 7.2).
|
|
18
19
|
|
|
19
20
|
Three projections of the same stream: ``format_live`` for the pane,
|
|
20
21
|
``format_log`` for the archive, and ``final_text`` for the caller that must
|
|
@@ -31,6 +32,14 @@ from typing import Any, Callable, Mapping
|
|
|
31
32
|
# previous one off the top of a short pane.
|
|
32
33
|
_MAX_SUMMARY = 120
|
|
33
34
|
|
|
35
|
+
# 화면이 받는 도구 결과 본문의 줄 수. 0 줄이던 동안 pane 에 남는 것은 성패와
|
|
36
|
+
# 바이트 수뿐이었다. 어떤 어댑터가 본문 대신 도구 이름표를 넘겨 모든 호출이
|
|
37
|
+
# 한 자릿수 바이트로 보이던 run 을, 화면만 보고는 아무도 판별하지 못했다.
|
|
38
|
+
# 결과 하나가 pane 을 다 차지하면 직전 호출이 위로 밀려 나가므로 앞부분만
|
|
39
|
+
# 보여주고, 나머지는 로그가 갖는다.
|
|
40
|
+
_LIVE_BODY_ROWS = 4
|
|
41
|
+
_BODY_INDENT = " "
|
|
42
|
+
|
|
34
43
|
|
|
35
44
|
@dataclass(frozen=True)
|
|
36
45
|
class Text:
|
|
@@ -58,11 +67,20 @@ class ToolResult:
|
|
|
58
67
|
``failed`` is None when the stream closed the tool step without reporting an
|
|
59
68
|
outcome at all. Reading that absence as success would put a confident "ok"
|
|
60
69
|
next to a command that failed.
|
|
70
|
+
|
|
71
|
+
``body_describes_result`` marks a tool that reported *about* its result
|
|
72
|
+
instead of returning it — a file read that answers ``54 lines, 4293 bytes``
|
|
73
|
+
rather than the 4293 bytes. Without the mark the two are the same shape and
|
|
74
|
+
the size reads as the payload, so a 4293-byte read is archived as
|
|
75
|
+
``← done (20 bytes)`` and a reader concludes the read came back nearly
|
|
76
|
+
empty. The provider that emits such a report says so; this layer only
|
|
77
|
+
renders it differently.
|
|
61
78
|
"""
|
|
62
79
|
|
|
63
80
|
body: str
|
|
64
81
|
size_bytes: int
|
|
65
82
|
failed: bool | None = None
|
|
83
|
+
body_describes_result: bool = False
|
|
66
84
|
|
|
67
85
|
|
|
68
86
|
@dataclass(frozen=True)
|
|
@@ -112,17 +130,6 @@ def host_event_row(
|
|
|
112
130
|
return row
|
|
113
131
|
|
|
114
132
|
|
|
115
|
-
def no_events(event: Mapping[str, Any]) -> tuple[StreamEvent, ...]:
|
|
116
|
-
"""The normaliser for a CLI that emits no events at all.
|
|
117
|
-
|
|
118
|
-
A text CLI's output is already what a person reads, so the runner forwards
|
|
119
|
-
it whole and never calls this. It exists so ``ExecCommand`` can default to
|
|
120
|
-
a truthful "there is nothing here to normalise" instead of to some
|
|
121
|
-
provider's schema.
|
|
122
|
-
"""
|
|
123
|
-
return ()
|
|
124
|
-
|
|
125
|
-
|
|
126
133
|
def content_block_events(event: Mapping[str, Any]) -> tuple[StreamEvent, ...]:
|
|
127
134
|
"""Normalise the stream shape keyed on ``type`` with ``message.content``.
|
|
128
135
|
|
|
@@ -157,7 +164,7 @@ def content_block_events(event: Mapping[str, Any]) -> tuple[StreamEvent, ...]:
|
|
|
157
164
|
|
|
158
165
|
|
|
159
166
|
def format_live(event: StreamEvent) -> list[str]:
|
|
160
|
-
return _rows(event, limit=_MAX_SUMMARY,
|
|
167
|
+
return _rows(event, limit=_MAX_SUMMARY, body_rows=_LIVE_BODY_ROWS)
|
|
161
168
|
|
|
162
169
|
|
|
163
170
|
def format_log(event: StreamEvent) -> list[str]:
|
|
@@ -167,7 +174,7 @@ def format_log(event: StreamEvent) -> list[str]:
|
|
|
167
174
|
the fact, by a person reconstructing why a worker failed and by
|
|
168
175
|
``okstra log-report``.
|
|
169
176
|
"""
|
|
170
|
-
return _rows(event, limit=None,
|
|
177
|
+
return _rows(event, limit=None, body_rows=None)
|
|
171
178
|
|
|
172
179
|
|
|
173
180
|
def final_text(event: StreamEvent) -> str | None:
|
|
@@ -175,17 +182,24 @@ def final_text(event: StreamEvent) -> str | None:
|
|
|
175
182
|
return event.text if isinstance(event, Result) else None
|
|
176
183
|
|
|
177
184
|
|
|
178
|
-
def _rows(event: StreamEvent, *, limit: int | None,
|
|
185
|
+
def _rows(event: StreamEvent, *, limit: int | None, body_rows: int | None) -> list[str]:
|
|
179
186
|
if isinstance(event, Text):
|
|
180
187
|
return _body_rows(event.body)
|
|
181
188
|
if isinstance(event, ToolCall):
|
|
182
189
|
head = f"→ {event.name}: {event.detail}" if event.detail else f"→ {event.name}"
|
|
183
190
|
return [_truncate(head, limit)]
|
|
184
191
|
if isinstance(event, ToolResult):
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
192
|
+
if event.body_describes_result:
|
|
193
|
+
# 크기를 싣지 않는다. 이 이벤트가 가진 숫자는 보고 문자열의 길이일
|
|
194
|
+
# 뿐이고(보고조차 없으면 0), 그것을 payload 자리에 놓는 것이 오독의
|
|
195
|
+
# 출처였다.
|
|
196
|
+
report = " ".join(event.body.split())
|
|
197
|
+
tail = (
|
|
198
|
+
f"the tool reported: {report}" if report else "the tool reported nothing"
|
|
199
|
+
)
|
|
200
|
+
return [_truncate(f" ← {_outcome(event.failed)} — no body returned; {tail}", limit)]
|
|
201
|
+
head = f" ← {_outcome(event.failed)} ({event.size_bytes} bytes)"
|
|
202
|
+
return [head, *_result_body(event.body, limit=limit, keep=body_rows)]
|
|
189
203
|
if isinstance(event, Denial):
|
|
190
204
|
return [_truncate(f"!! PERMISSION DENIED — {event.tool}: {event.reason}", limit)]
|
|
191
205
|
# A Result is printed by the runner at the point the run ends, not woven
|
|
@@ -193,6 +207,23 @@ def _rows(event: StreamEvent, *, limit: int | None, include_body: bool) -> list[
|
|
|
193
207
|
return []
|
|
194
208
|
|
|
195
209
|
|
|
210
|
+
def _result_body(body: str, *, limit: int | None, keep: int | None) -> list[str]:
|
|
211
|
+
"""결과 본문을 몇 줄 보여줄지 정한다.
|
|
212
|
+
|
|
213
|
+
`keep is None` 이 기록의 몫이다 — 전부 남긴다. 화면은 앞 몇 줄만 받고,
|
|
214
|
+
생략한 줄 수를 밝힌다. 밝히지 않으면 짧은 출력과 잘린 출력이 같은 모양이
|
|
215
|
+
되어, 읽는 사람이 로그를 열 이유를 알 수 없다.
|
|
216
|
+
"""
|
|
217
|
+
rows = _body_rows(body)
|
|
218
|
+
if keep is None:
|
|
219
|
+
return rows
|
|
220
|
+
shown = [_truncate(_BODY_INDENT + row, limit) for row in rows[:keep]]
|
|
221
|
+
dropped = len(rows) - keep
|
|
222
|
+
if dropped > 0:
|
|
223
|
+
shown.append(f"{_BODY_INDENT}… +{dropped} more line(s) — full body in the log")
|
|
224
|
+
return shown
|
|
225
|
+
|
|
226
|
+
|
|
196
227
|
def _outcome(failed: bool | None) -> str:
|
|
197
228
|
if failed is None:
|
|
198
229
|
return "done"
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
"""쓰기 정책 값 타입 · 직렬화 · 검증.
|
|
2
|
+
|
|
3
|
+
`..write_policy`(623줄) 안에 있던 값 코어를 도메인으로 내렸다. 그 모듈은
|
|
4
|
+
git 을 실행하고 run manifest 를 읽는 응용 로직인데, `domain/worker_exec.py` 가
|
|
5
|
+
`WritePolicy` / `WriteEnforcement` 타입 하나 때문에 그 응용 모듈을 최상위
|
|
6
|
+
import 하고 있었다 — 도메인이 응용을 부르는 역전이고, 그 탓에
|
|
7
|
+
`write_policy → path_hints` 가 함수 본문 지연 import 로 밀려나 있었다.
|
|
8
|
+
|
|
9
|
+
값과 그 값의 정합성(직렬화·다이제스트·payload 검증)까지가 여기다.
|
|
10
|
+
정책을 **만들거나**(build_*) **유도하는**(derive_*) 일은 응용에 남는다.
|
|
11
|
+
`..write_policy` 가 이 이름들을 재노출하므로 기존 호출부는 바뀌지 않는다.
|
|
12
|
+
"""
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import hashlib
|
|
16
|
+
import json
|
|
17
|
+
from collections.abc import Mapping, Sequence
|
|
18
|
+
from dataclasses import dataclass
|
|
19
|
+
from pathlib import Path, PurePosixPath
|
|
20
|
+
from typing import Any, Literal
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
BoundaryPrecision = Literal["exact-path", "directory-boundary", "none"]
|
|
24
|
+
|
|
25
|
+
_PRECISION_RANK = {"none": 0, "directory-boundary": 1, "exact-path": 2}
|
|
26
|
+
|
|
27
|
+
_POLICY_KEYS = {
|
|
28
|
+
"artifactPolicy",
|
|
29
|
+
"sourcePolicy",
|
|
30
|
+
"gitPolicy",
|
|
31
|
+
"auxiliaryPolicy",
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
_ENFORCEMENT_KEYS = {
|
|
35
|
+
"boundaryPrecision",
|
|
36
|
+
"mutationAudit",
|
|
37
|
+
"attribution",
|
|
38
|
+
"auditedRoots",
|
|
39
|
+
"observationCoverage",
|
|
40
|
+
"unobservedWriteSurfaces",
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
class WritePolicyError(ValueError):
|
|
44
|
+
"""Raised when a write policy cannot be made canonical or trustworthy."""
|
|
45
|
+
|
|
46
|
+
@dataclass(frozen=True)
|
|
47
|
+
class WritePolicy:
|
|
48
|
+
artifact_policy: Mapping[str, Any]
|
|
49
|
+
source_policy: Mapping[str, Any]
|
|
50
|
+
git_policy: Mapping[str, Any]
|
|
51
|
+
auxiliary_policy: Mapping[str, Any]
|
|
52
|
+
maximum_boundary_precision: BoundaryPrecision = "none"
|
|
53
|
+
|
|
54
|
+
def to_payload(self) -> dict[str, Any]:
|
|
55
|
+
return {
|
|
56
|
+
"artifactPolicy": dict(self.artifact_policy),
|
|
57
|
+
"sourcePolicy": dict(self.source_policy),
|
|
58
|
+
"gitPolicy": dict(self.git_policy),
|
|
59
|
+
"auxiliaryPolicy": dict(self.auxiliary_policy),
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
def digest(self) -> str:
|
|
64
|
+
return write_policy_digest(self.to_payload())
|
|
65
|
+
|
|
66
|
+
@property
|
|
67
|
+
def source_mode(self) -> str:
|
|
68
|
+
return str(self.source_policy["mode"])
|
|
69
|
+
|
|
70
|
+
@dataclass(frozen=True)
|
|
71
|
+
class WriteEnforcement:
|
|
72
|
+
boundary_precision: BoundaryPrecision
|
|
73
|
+
mutation_audit: Literal["none", "batch"]
|
|
74
|
+
attribution: Literal["call", "batch-unattributed"]
|
|
75
|
+
audited_roots: tuple[Path, ...]
|
|
76
|
+
observation_coverage: Literal["complete", "scoped", "partial"]
|
|
77
|
+
unobserved_write_surfaces: tuple[str, ...]
|
|
78
|
+
|
|
79
|
+
def to_payload(self) -> dict[str, Any]:
|
|
80
|
+
return {
|
|
81
|
+
"boundaryPrecision": self.boundary_precision,
|
|
82
|
+
"mutationAudit": self.mutation_audit,
|
|
83
|
+
"attribution": self.attribution,
|
|
84
|
+
"auditedRoots": [str(path) for path in self.audited_roots],
|
|
85
|
+
"observationCoverage": self.observation_coverage,
|
|
86
|
+
"unobservedWriteSurfaces": list(self.unobserved_write_surfaces),
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
def write_policy_from_payload(payload: Mapping[str, Any]) -> WritePolicy:
|
|
90
|
+
validate_write_policy_payload(payload)
|
|
91
|
+
return WritePolicy(
|
|
92
|
+
artifact_policy=dict(payload["artifactPolicy"]),
|
|
93
|
+
source_policy=dict(payload["sourcePolicy"]),
|
|
94
|
+
git_policy=dict(payload["gitPolicy"]),
|
|
95
|
+
auxiliary_policy=dict(payload["auxiliaryPolicy"]),
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
def write_enforcement_from_payload(
|
|
99
|
+
payload: Mapping[str, Any],
|
|
100
|
+
) -> WriteEnforcement:
|
|
101
|
+
validate_write_enforcement_payload(payload)
|
|
102
|
+
return WriteEnforcement(
|
|
103
|
+
boundary_precision=payload["boundaryPrecision"],
|
|
104
|
+
mutation_audit=payload["mutationAudit"],
|
|
105
|
+
attribution=payload["attribution"],
|
|
106
|
+
audited_roots=tuple(Path(value) for value in payload["auditedRoots"]),
|
|
107
|
+
observation_coverage=payload["observationCoverage"],
|
|
108
|
+
unobserved_write_surfaces=tuple(payload["unobservedWriteSurfaces"]),
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
def write_policy_digest(payload: Mapping[str, Any]) -> str:
|
|
112
|
+
validate_write_policy_payload(payload)
|
|
113
|
+
encoded = json.dumps(
|
|
114
|
+
payload,
|
|
115
|
+
sort_keys=True,
|
|
116
|
+
separators=(",", ":"),
|
|
117
|
+
ensure_ascii=False,
|
|
118
|
+
).encode("utf-8")
|
|
119
|
+
return "sha256:" + hashlib.sha256(encoded).hexdigest()
|
|
120
|
+
|
|
121
|
+
def validate_write_policy_payload(payload: Mapping[str, Any]) -> None:
|
|
122
|
+
if set(payload) != _POLICY_KEYS:
|
|
123
|
+
raise WritePolicyError("writePolicy must contain four canonical policy sections")
|
|
124
|
+
artifact = _mapping(payload.get("artifactPolicy"), "artifactPolicy")
|
|
125
|
+
source = _mapping(payload.get("sourcePolicy"), "sourcePolicy")
|
|
126
|
+
git = _mapping(payload.get("gitPolicy"), "gitPolicy")
|
|
127
|
+
auxiliary = _mapping(payload.get("auxiliaryPolicy"), "auxiliaryPolicy")
|
|
128
|
+
if set(artifact) != {"allowedRoot", "allowedPaths"}:
|
|
129
|
+
raise WritePolicyError("artifactPolicy is incomplete")
|
|
130
|
+
_absolute_text(artifact.get("allowedRoot"), "artifactPolicy.allowedRoot")
|
|
131
|
+
_relative_paths(artifact.get("allowedPaths", ()))
|
|
132
|
+
if source.get("mode") not in {"source-readonly", "project-mutation"}:
|
|
133
|
+
raise WritePolicyError("sourcePolicy mode is invalid")
|
|
134
|
+
if set(source) != {"mode", "allowedRoot", "plannedPaths", "protectedPaths"}:
|
|
135
|
+
raise WritePolicyError("sourcePolicy is incomplete")
|
|
136
|
+
_absolute_text(source.get("allowedRoot"), "sourcePolicy.allowedRoot")
|
|
137
|
+
_relative_paths(source.get("plannedPaths", ()))
|
|
138
|
+
_relative_paths(source.get("protectedPaths", ()))
|
|
139
|
+
if git.get("mode") not in {"disabled", "fast-forward-descendant-chain"}:
|
|
140
|
+
raise WritePolicyError("gitPolicy mode is invalid")
|
|
141
|
+
if git.get("mode") == "disabled" and set(git) != {"mode"}:
|
|
142
|
+
raise WritePolicyError("disabled gitPolicy must not grant Git paths")
|
|
143
|
+
required_git = {
|
|
144
|
+
"mode", "worktreeGitDir", "gitCommonDir", "expectedBaseCommit",
|
|
145
|
+
"allowedBranchRef", "commitMode",
|
|
146
|
+
}
|
|
147
|
+
if git.get("mode") != "disabled" and set(git) != required_git:
|
|
148
|
+
raise WritePolicyError("project mutation gitPolicy is incomplete")
|
|
149
|
+
if set(auxiliary) != {"scratchRoots", "generatedPaths", "externalRoots"}:
|
|
150
|
+
raise WritePolicyError("auxiliaryPolicy is incomplete")
|
|
151
|
+
_relative_paths(auxiliary.get("generatedPaths", ()))
|
|
152
|
+
for key in ("scratchRoots", "externalRoots"):
|
|
153
|
+
for value in _text_sequence(auxiliary.get(key), f"auxiliaryPolicy.{key}"):
|
|
154
|
+
_absolute_text(value, f"auxiliaryPolicy.{key}")
|
|
155
|
+
|
|
156
|
+
def validate_write_enforcement_payload(payload: Mapping[str, Any]) -> None:
|
|
157
|
+
if set(payload) != _ENFORCEMENT_KEYS:
|
|
158
|
+
raise WritePolicyError("writeEnforcement is incomplete")
|
|
159
|
+
boundary = payload.get("boundaryPrecision")
|
|
160
|
+
mutation_audit = payload.get("mutationAudit")
|
|
161
|
+
attribution = payload.get("attribution")
|
|
162
|
+
coverage = payload.get("observationCoverage")
|
|
163
|
+
if boundary not in _PRECISION_RANK:
|
|
164
|
+
raise WritePolicyError("writeEnforcement boundaryPrecision is invalid")
|
|
165
|
+
if mutation_audit not in {"none", "batch"}:
|
|
166
|
+
raise WritePolicyError("writeEnforcement mutationAudit is invalid")
|
|
167
|
+
if attribution not in {"call", "batch-unattributed"}:
|
|
168
|
+
raise WritePolicyError("writeEnforcement attribution is invalid")
|
|
169
|
+
if coverage not in {"complete", "scoped", "partial"}:
|
|
170
|
+
raise WritePolicyError("writeEnforcement observationCoverage is invalid")
|
|
171
|
+
if (boundary, mutation_audit, attribution) == ("exact-path", "none", "call"):
|
|
172
|
+
if coverage != "complete":
|
|
173
|
+
raise WritePolicyError("exact enforcement must have complete coverage")
|
|
174
|
+
elif (boundary, mutation_audit, attribution) == (
|
|
175
|
+
"none",
|
|
176
|
+
"none",
|
|
177
|
+
"batch-unattributed",
|
|
178
|
+
):
|
|
179
|
+
if coverage != "partial":
|
|
180
|
+
raise WritePolicyError("host-session enforcement must be partial")
|
|
181
|
+
elif mutation_audit != "batch" or attribution != "batch-unattributed":
|
|
182
|
+
raise WritePolicyError("non-exact enforcement must use batch attribution")
|
|
183
|
+
elif boundary == "exact-path" or coverage == "complete":
|
|
184
|
+
raise WritePolicyError("batch enforcement cannot claim exact coverage")
|
|
185
|
+
for root in _text_sequence(payload.get("auditedRoots"), "auditedRoots"):
|
|
186
|
+
_absolute_text(root, "auditedRoots")
|
|
187
|
+
_text_sequence(payload.get("unobservedWriteSurfaces"), "unobservedWriteSurfaces")
|
|
188
|
+
|
|
189
|
+
def _relative_paths(value: object) -> tuple[str, ...]:
|
|
190
|
+
paths: list[str] = []
|
|
191
|
+
for raw in _text_sequence(value, "relative paths"):
|
|
192
|
+
candidate = PurePosixPath(raw)
|
|
193
|
+
if candidate.is_absolute() or not candidate.parts or ".." in candidate.parts:
|
|
194
|
+
raise WritePolicyError("policy paths must be normalized relative paths")
|
|
195
|
+
normalized = candidate.as_posix()
|
|
196
|
+
if normalized in {"", "."} or normalized != raw:
|
|
197
|
+
raise WritePolicyError("policy paths must be normalized relative paths")
|
|
198
|
+
paths.append(normalized)
|
|
199
|
+
if len(set(paths)) != len(paths):
|
|
200
|
+
raise WritePolicyError("policy paths must be unique")
|
|
201
|
+
return tuple(paths)
|
|
202
|
+
|
|
203
|
+
def _mapping(value: object, label: str) -> Mapping[str, Any]:
|
|
204
|
+
if not isinstance(value, Mapping):
|
|
205
|
+
raise WritePolicyError(f"{label} must be an object")
|
|
206
|
+
return value
|
|
207
|
+
|
|
208
|
+
def _text_sequence(value: object, label: str) -> tuple[str, ...]:
|
|
209
|
+
if not isinstance(value, Sequence) or isinstance(value, (str, bytes)):
|
|
210
|
+
raise WritePolicyError(f"{label} must be an array")
|
|
211
|
+
rows = tuple(value)
|
|
212
|
+
if any(not isinstance(row, str) or not row for row in rows):
|
|
213
|
+
raise WritePolicyError(f"{label} must contain non-empty strings")
|
|
214
|
+
return rows
|
|
215
|
+
|
|
216
|
+
def _absolute_text(value: object, label: str) -> str:
|
|
217
|
+
if not isinstance(value, str) or not value or not Path(value).is_absolute():
|
|
218
|
+
raise WritePolicyError(f"{label} must be an absolute path")
|
|
219
|
+
return value
|
|
@@ -9,7 +9,7 @@ from contextlib import contextmanager
|
|
|
9
9
|
from dataclasses import asdict
|
|
10
10
|
from pathlib import Path
|
|
11
11
|
|
|
12
|
-
from ..
|
|
12
|
+
from ..agent.invocation import (
|
|
13
13
|
AgentInvocationError,
|
|
14
14
|
AgentModelAssignment,
|
|
15
15
|
agent_model_assignment_from_payload,
|
|
@@ -113,7 +113,7 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
113
113
|
resolve.add_argument(
|
|
114
114
|
"--capability",
|
|
115
115
|
action="append",
|
|
116
|
-
choices=("claude-skill-handoff",
|
|
116
|
+
choices=("claude-skill-handoff",),
|
|
117
117
|
default=[],
|
|
118
118
|
)
|
|
119
119
|
launch = commands.add_parser("launch-plan")
|
|
@@ -200,9 +200,16 @@ def _probe_payload(
|
|
|
200
200
|
status = "unknown" if "unavailable" in statuses else (
|
|
201
201
|
"ready" if readiness.ok else "blocked"
|
|
202
202
|
)
|
|
203
|
+
# A check may report that this session cannot host the lead while the run
|
|
204
|
+
# itself is fine. The caller needs the mode it must actually launch in, not
|
|
205
|
+
# the mode it asked about, so the probe answers with the resolved one.
|
|
206
|
+
entry_mode = (
|
|
207
|
+
"spawn-process" if "lead-must-spawn" in statuses else args.entry_mode
|
|
208
|
+
)
|
|
203
209
|
return {
|
|
204
210
|
"runtime": adapter.descriptor.id,
|
|
205
211
|
"status": status,
|
|
212
|
+
"entryMode": entry_mode,
|
|
206
213
|
"checks": list(readiness.checks),
|
|
207
214
|
"relayContract": adapter.descriptor.relay_contract,
|
|
208
215
|
}
|
|
@@ -210,8 +210,22 @@ def render_result_text(result: dict) -> str:
|
|
|
210
210
|
return "".join(parts)
|
|
211
211
|
|
|
212
212
|
|
|
213
|
+
_CLI_EPILOG = r"""Usage:
|
|
214
|
+
okstra error-report <task-root>
|
|
215
|
+
okstra error-report <task-key> --project-root <dir>
|
|
216
|
+
okstra error-report <task-key> --cwd <dir>
|
|
217
|
+
|
|
218
|
+
Writes a timestamped markdown report under <task-root>/error-reports/ and
|
|
219
|
+
prints a JSON summary (counts by errorType / source / phase / agent) to stdout.
|
|
220
|
+
This is read-only against task state; it never mutates task artifacts.
|
|
221
|
+
"""
|
|
222
|
+
|
|
223
|
+
|
|
213
224
|
def main(argv: list[str] | None = None) -> int:
|
|
214
225
|
parser = argparse.ArgumentParser(
|
|
226
|
+
prog="okstra error-report",
|
|
227
|
+
epilog=_CLI_EPILOG,
|
|
228
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
215
229
|
description="Aggregate okstra-run error logs into a task-level report."
|
|
216
230
|
)
|
|
217
231
|
parser.add_argument("target", help="task root path or task-key")
|
|
@@ -19,7 +19,11 @@ from okstra_ctl.error_log_core import aggregate, parse_records
|
|
|
19
19
|
from okstra_ctl.jsonl import read_jsonl
|
|
20
20
|
from okstra_ctl.locks import central_lock
|
|
21
21
|
from okstra_ctl.paths import okstra_home, resolve_under_root
|
|
22
|
-
from okstra_ctl.json_boundary import
|
|
22
|
+
from okstra_ctl.json_boundary import (
|
|
23
|
+
JsonBoundaryError,
|
|
24
|
+
load_owned_object,
|
|
25
|
+
write_owned_object_atomic,
|
|
26
|
+
)
|
|
23
27
|
|
|
24
28
|
|
|
25
29
|
def run_dirs(home: Path) -> list[tuple[str, Path, dict]]:
|
|
@@ -282,7 +286,7 @@ def last_output_path(home: Path) -> str:
|
|
|
282
286
|
"lastOutputPath", ""
|
|
283
287
|
)
|
|
284
288
|
)
|
|
285
|
-
except
|
|
289
|
+
except JsonBoundaryError:
|
|
286
290
|
return ""
|
|
287
291
|
|
|
288
292
|
|
|
@@ -335,8 +339,20 @@ def render_result_text(result: dict) -> str:
|
|
|
335
339
|
return "".join(lines)
|
|
336
340
|
|
|
337
341
|
|
|
342
|
+
_CLI_EPILOG = r"""Usage:
|
|
343
|
+
okstra error-zip --out <path>
|
|
344
|
+
|
|
345
|
+
Walks the global run-index (~/.okstra/recent.jsonl + active.jsonl), collects
|
|
346
|
+
every target project's runs/*/logs/errors-*.jsonl, anonymizes records, and
|
|
347
|
+
writes a .zip (report.md + errors/anonymized.jsonl) to <path>. Read-only
|
|
348
|
+
against every target's .okstra/. Prints a JSON summary to stdout.
|
|
349
|
+
"""
|
|
350
|
+
|
|
351
|
+
|
|
338
352
|
def main(argv: list[str] | None = None) -> int:
|
|
339
353
|
parser = argparse.ArgumentParser(
|
|
354
|
+
epilog=_CLI_EPILOG,
|
|
355
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
340
356
|
prog="okstra error-zip",
|
|
341
357
|
description="cross-project okstra 에러를 수집·익명화해 zip 으로 묶는다.",
|
|
342
358
|
)
|
|
@@ -7,6 +7,11 @@ from dataclasses import dataclass
|
|
|
7
7
|
from typing import Literal
|
|
8
8
|
|
|
9
9
|
|
|
10
|
+
# 판정 우선순위 — 여러 조건이 동시에 성립할 때 앞선 것이 이긴다. 저작 계약이
|
|
11
|
+
# 이 순서를 작성자에게 말하므로 `calculate_exact_coverage` 의 분기와 함께 간다.
|
|
12
|
+
COVERAGE_VERDICT_PRECEDENCE = ("contradicted", "under", "over", "exact")
|
|
13
|
+
|
|
14
|
+
|
|
10
15
|
class ExactCoverageError(ValueError):
|
|
11
16
|
"""Raised when coverage input cannot define a valid comparison."""
|
|
12
17
|
|