okstra 0.186.7 → 0.187.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 +104 -39
- 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 +37 -36
- 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 +25 -18
- 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/convergence.md +96 -87
- 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 +34 -4
- 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 +40 -22
- package/runtime/python/okstra_ctl/adapters/providers/claude/adapter.py +14 -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} +12 -1
- 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 +215 -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 +408 -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 +260 -8
- 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 +16 -2
- package/runtime/python/okstra_ctl/contract_graph_cli.py +7 -0
- package/runtime/python/okstra_ctl/convergence.py +101 -15
- 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 +102 -25
- package/runtime/python/okstra_ctl/dispatch_state.py +268 -39
- 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 +21 -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/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 +90 -16
- 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 +25 -11
- 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 +3 -7
- 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 +206 -15
- 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 +90 -3
- package/runtime/python/okstra_ctl/report_projections.py +8 -2
- package/runtime/python/okstra_ctl/report_synthesis_packet.py +258 -1
- package/runtime/python/okstra_ctl/report_translation.py +2 -35
- 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/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 +16 -9
- 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/claude.py +9 -1
- package/runtime/python/okstra_token_usage/cli.py +3 -1
- package/runtime/python/okstra_token_usage/collect.py +71 -10
- 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 +19 -7
- 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 +1 -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 +1369 -2654
- 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
|
@@ -17,9 +17,7 @@ from __future__ import annotations
|
|
|
17
17
|
|
|
18
18
|
import argparse
|
|
19
19
|
import json
|
|
20
|
-
import os
|
|
21
20
|
import re
|
|
22
|
-
import shlex
|
|
23
21
|
import subprocess
|
|
24
22
|
import sys
|
|
25
23
|
import time
|
|
@@ -35,34 +33,6 @@ OKSTRA_LABEL_KEYS = ("okstra.task-key", "okstra.project-name", "okstra.run-trace
|
|
|
35
33
|
|
|
36
34
|
COMPOSE_FILENAME = "docker-compose.yml"
|
|
37
35
|
|
|
38
|
-
# spec §5/§10 — 에러 트리거 화이트리스트. 이 정규식 중 하나라도 라인에 걸려야
|
|
39
|
-
# 2단계(LLM analyze)로 넘어간다. 건강한 컨테이너는 어느 것도 안 걸려 토큰 0.
|
|
40
|
-
ERROR_PATTERNS = (
|
|
41
|
-
re.compile(r"\bERROR\b"),
|
|
42
|
-
re.compile(r"\bFATAL\b"),
|
|
43
|
-
re.compile(r"\bCRITICAL\b"),
|
|
44
|
-
re.compile(r"\bPANIC\b"),
|
|
45
|
-
re.compile(r"\b\w+(?:Error|Exception)\b"),
|
|
46
|
-
re.compile(r"^Traceback \(most recent call last\):"),
|
|
47
|
-
re.compile(r"\b(?:exit(?:ed)?|exit code|status)\s+(?:code\s+)?[1-9]\d*\b", re.I),
|
|
48
|
-
re.compile(r"\bUnhandled\b", re.I),
|
|
49
|
-
)
|
|
50
|
-
|
|
51
|
-
# error_signature 정규화: 변동성 노이즈(타임스탬프/pid/라인참조/hex 주소)만 깎고,
|
|
52
|
-
# ERROR_PATTERNS 가 *탐지하는* 의미 있는 코드(HTTP status, exit/signal code)는 보존한다.
|
|
53
|
-
# 블랭킷 `\d+` 치환은 distinct 에러(500 vs 404, exit 1 vs 137)를 한 시그니처로
|
|
54
|
-
# collapse 시켜 디바운스가 영영 분석을 막으므로 쓰지 않는다(spec §10 signal vs noise).
|
|
55
|
-
_NOISE_SUBS = (
|
|
56
|
-
(re.compile(
|
|
57
|
-
r"\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}:\d{2}(?:[.,]\d+)?(?:Z|[+-]\d{2}:?\d{2})?"
|
|
58
|
-
), "<ts>"),
|
|
59
|
-
(re.compile(r"\b0x[0-9a-fA-F]+\b"), "<hex>"),
|
|
60
|
-
(re.compile(r"\b(pid|tid|ppid)=\d+", re.I), r"\1=<n>"),
|
|
61
|
-
(re.compile(r":\d+:"), ":<n>:"),
|
|
62
|
-
)
|
|
63
|
-
_WS_RE = re.compile(r"\s+")
|
|
64
|
-
|
|
65
|
-
|
|
66
36
|
def merge_env(base_env: dict, override: dict) -> dict:
|
|
67
37
|
"""base_env 위에 override 를 얹는다. 동일 키는 override 가 이긴다."""
|
|
68
38
|
return {**base_env, **override}
|
|
@@ -126,179 +96,6 @@ def build_compose_up_argv(
|
|
|
126
96
|
return argv
|
|
127
97
|
|
|
128
98
|
|
|
129
|
-
def scan_log_chunk(text: str, patterns: Iterable[re.Pattern]) -> list[str]:
|
|
130
|
-
"""ERROR_PATTERNS 중 하나라도 걸리는 라인만 순서대로 반환(순수, docker 무의존)."""
|
|
131
|
-
return [
|
|
132
|
-
line
|
|
133
|
-
for line in text.splitlines()
|
|
134
|
-
if line and any(p.search(line) for p in patterns)
|
|
135
|
-
]
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
def error_signature(line: str) -> str:
|
|
139
|
-
"""디바운스 키: 변동성 노이즈만 깎고 의미 있는 코드는 보존한 라인.
|
|
140
|
-
|
|
141
|
-
타임스탬프/pid/라인참조/hex 주소는 placeholder 로 정규화하지만 HTTP status·
|
|
142
|
-
exit/signal code 같은 판별자는 그대로 둔다. 같은 에러가 timestamp·pid 만
|
|
143
|
-
달리해 반복되면 동일 키 → analyze 1회(spec §10 디바운스), 그러나 500 vs 404
|
|
144
|
-
처럼 의미가 다르면 별도 키 → 각각 분석된다.
|
|
145
|
-
"""
|
|
146
|
-
s = line
|
|
147
|
-
for pattern, repl in _NOISE_SUBS:
|
|
148
|
-
s = pattern.sub(repl, s)
|
|
149
|
-
return _WS_RE.sub(" ", s).strip()
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
def watcher_step(
|
|
153
|
-
*,
|
|
154
|
-
log_chunk: str,
|
|
155
|
-
seen_signatures: set,
|
|
156
|
-
findings_path: Path,
|
|
157
|
-
analyze: Callable[[str], object],
|
|
158
|
-
) -> int:
|
|
159
|
-
"""log_chunk 를 1단계 스캔 → 새 시그니처마다 analyze 호출 + findings append.
|
|
160
|
-
|
|
161
|
-
이미 본 시그니처는 디바운스(skip). 새 분석 건수를 반환한다. analyze 는 주입이라
|
|
162
|
-
유닛 테스트는 스텁으로 LLM 없이 행위를 검증한다.
|
|
163
|
-
"""
|
|
164
|
-
new_count = 0
|
|
165
|
-
for line in scan_log_chunk(log_chunk, ERROR_PATTERNS):
|
|
166
|
-
sig = error_signature(line)
|
|
167
|
-
if sig in seen_signatures:
|
|
168
|
-
continue
|
|
169
|
-
seen_signatures.add(sig)
|
|
170
|
-
# 탐지 자체를 analyze 보다 먼저 영속화한다 — analyze(=pane spawn) 가 실패해도
|
|
171
|
-
# 에러 라인은 findings 에 남는다. analyze 예외가 장기 watcher 루프를 죽이지
|
|
172
|
-
# 않도록 격리하고, 실패 사실도 findings 에 기록한다(silent 중단 방지).
|
|
173
|
-
_append_finding(Path(findings_path), sig, line)
|
|
174
|
-
try:
|
|
175
|
-
analyze(line)
|
|
176
|
-
except Exception as exc: # noqa: BLE001 — best-effort 외부 spawn, 루프 보존이 우선
|
|
177
|
-
_append_finding(Path(findings_path), sig, f"[analyze 실패] {exc}")
|
|
178
|
-
new_count += 1
|
|
179
|
-
return new_count
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
def _append_finding(findings_path: Path, signature: str, line: str) -> None:
|
|
183
|
-
findings_path.parent.mkdir(parents=True, exist_ok=True)
|
|
184
|
-
block = f"## {signature}\n\n```\n{line}\n```\n\n"
|
|
185
|
-
with findings_path.open("a", encoding="utf-8") as fh:
|
|
186
|
-
fh.write(block)
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
# spec §9 — watcher AI 는 "탐지·보고만, 자동 치유 제외". 기본 analyze 가 기동하는
|
|
190
|
-
# 에이전트는 findings 파일 append 외 어떤 코드/설정 Edit/Write 도 금지한다. 이
|
|
191
|
-
# 계약 문구를 에이전트 프롬프트에 그대로 넣어 enforcement 를 명시한다.
|
|
192
|
-
WATCHER_READONLY_CONTRACT = (
|
|
193
|
-
"You are a READ-ONLY container log watcher. You MUST NOT Edit or Write any "
|
|
194
|
-
"code or config file. Your ONLY permitted write is appending an analysis "
|
|
195
|
-
"note to the findings file. Detect and report; never auto-fix."
|
|
196
|
-
)
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
def default_analyze(findings_path: Path, *, session_pane: str | None = None):
|
|
200
|
-
"""프로덕션 기본 analyze 팩토리: read-only 계약을 건 에이전트를 pane 으로 기동.
|
|
201
|
-
|
|
202
|
-
codex/antigravity-worker 와 동일한 split_container_pane 메커니즘을 쓰되
|
|
203
|
-
WATCHER_READONLY_CONTRACT 를 프롬프트에 박아 코드/설정 수정 권한을 차단한다.
|
|
204
|
-
라이브 spawn 세부(에이전트 CLI·모델·권한 스코프)는 아직 미구현이라 e2e 영역으로
|
|
205
|
-
둔다 — session_pane 이 없으면 split_container_pane 이 guard 로 None 을 돌려주어
|
|
206
|
-
실제 spawn 없이 no-op 이 된다(에러 탐지·findings 기록은 watcher_step 이 담당).
|
|
207
|
-
프롬프트를 셸 명령으로 그대로 실행하지 않도록, 자동 `$TMUX_PANE` fallback 은
|
|
208
|
-
두지 않는다 — 자연어 프롬프트가 pane 명령으로 새지 않게 하는 안전장치다.
|
|
209
|
-
"""
|
|
210
|
-
from okstra_ctl import tmux
|
|
211
|
-
|
|
212
|
-
def analyze(window: str) -> str | None:
|
|
213
|
-
prompt = f"{WATCHER_READONLY_CONTRACT}\n\nError window:\n{window}\n"
|
|
214
|
-
return tmux.split_container_pane(
|
|
215
|
-
session_pane=session_pane,
|
|
216
|
-
cwd=str(Path(findings_path).parent),
|
|
217
|
-
command=prompt,
|
|
218
|
-
title="okstra-watcher",
|
|
219
|
-
scope_value=str(findings_path),
|
|
220
|
-
kind="watcher",
|
|
221
|
-
)
|
|
222
|
-
|
|
223
|
-
return analyze
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
def build_watcher_pane_command(
|
|
227
|
-
*, python_exe: str, module_root: str, project_name: str,
|
|
228
|
-
service: str, findings_path: str, scan_interval_seconds: float,
|
|
229
|
-
) -> str:
|
|
230
|
-
"""watcher pane 이 실행할 셸 명령 문자열.
|
|
231
|
-
|
|
232
|
-
`python -m okstra_ctl.container watch` 를 PYTHONPATH 와 함께 박는다 — tmux pane
|
|
233
|
-
의 환경 상속에 의존하면 PYTHONPATH 가 비어 import 가 깨지므로, 현재 프로세스의
|
|
234
|
-
module-root 를 명시적으로 넘긴다. 모든 인자는 shlex 로 인용해 경로에 공백/메타
|
|
235
|
-
문자가 있어도 안전하다."""
|
|
236
|
-
return " ".join([
|
|
237
|
-
f"PYTHONPATH={shlex.quote(module_root)}",
|
|
238
|
-
shlex.quote(python_exe), "-m", "okstra_ctl.container", "watch",
|
|
239
|
-
"--project-name", shlex.quote(project_name),
|
|
240
|
-
"--service", shlex.quote(service),
|
|
241
|
-
"--findings", shlex.quote(findings_path),
|
|
242
|
-
"--scan-interval", str(scan_interval_seconds),
|
|
243
|
-
])
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
def run_service_watcher(
|
|
247
|
-
*, project_name: str, service: str, findings_path: str,
|
|
248
|
-
scan_interval_seconds: float,
|
|
249
|
-
) -> None:
|
|
250
|
-
"""`container watch` 서브커맨드 본체 — 한 서비스의 감시 루프를 돈다.
|
|
251
|
-
|
|
252
|
-
stage ① 경량 스캔(ERROR 패턴) → 매칭 시 findings append + stage ② analyze.
|
|
253
|
-
사용자가 pane/세션을 종료(stop-watcher/down)할 때까지 지속한다."""
|
|
254
|
-
analyze = default_analyze(Path(findings_path))
|
|
255
|
-
run_watcher_loop(
|
|
256
|
-
service=service, project_name=project_name,
|
|
257
|
-
findings_path=Path(findings_path),
|
|
258
|
-
scan_interval_seconds=scan_interval_seconds, analyze=analyze,
|
|
259
|
-
)
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
def run_watcher_loop(
|
|
263
|
-
*,
|
|
264
|
-
service: str,
|
|
265
|
-
project_name: str,
|
|
266
|
-
findings_path: Path,
|
|
267
|
-
scan_interval_seconds: float,
|
|
268
|
-
analyze: Callable[[str], object],
|
|
269
|
-
) -> None:
|
|
270
|
-
"""`docker compose -p <project> logs --since <last>` 증분 fetch + sleep 루프.
|
|
271
|
-
|
|
272
|
-
사용자 종료(stop-watcher/down) 까지 지속. 토큰은 ERROR_PATTERNS 가 걸릴 때만
|
|
273
|
-
쓴다. 루프는 얇게 — 매 tick 의 판단은 watcher_step 에 위임한다.
|
|
274
|
-
"""
|
|
275
|
-
seen: set = set()
|
|
276
|
-
# 첫 tick 은 --since 없이 컨테이너 기동 이후 backlog 전체를 본다. `--since 0s`
|
|
277
|
-
# 는 'now 이후'를 의미해 기동 중 찍힌 startup-failure 에러를 영구히 놓친다.
|
|
278
|
-
since: str | None = None
|
|
279
|
-
while True:
|
|
280
|
-
tick_start = time.monotonic()
|
|
281
|
-
chunk = _fetch_log_chunk(service=service, project_name=project_name, since=since)
|
|
282
|
-
watcher_step(
|
|
283
|
-
log_chunk=chunk, seen_signatures=seen,
|
|
284
|
-
findings_path=Path(findings_path), analyze=analyze,
|
|
285
|
-
)
|
|
286
|
-
time.sleep(scan_interval_seconds)
|
|
287
|
-
# 다음 윈도우는 '직전 fetch 이후 실제 경과시간'(sleep + analyze 포함)을
|
|
288
|
-
# 덮는다. 고정 interval 윈도우는 analyze 가 interval 보다 오래 걸리면 그
|
|
289
|
-
# 사이 찍힌 에러 라인을 영구히 놓친다. +1s 오버랩의 중복은 디바운스가 흡수.
|
|
290
|
-
since = f"{int(time.monotonic() - tick_start) + 1}s"
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
def _fetch_log_chunk(*, service: str, project_name: str, since: str | None) -> str:
|
|
294
|
-
argv = ["docker", "compose", "-p", project_name, "logs", "--no-color"]
|
|
295
|
-
if since: # 첫 tick(None)은 --since 생략 → 기동 backlog 전체
|
|
296
|
-
argv += ["--since", since]
|
|
297
|
-
argv.append(service)
|
|
298
|
-
result = subprocess.run(argv, capture_output=True, text=True, check=False)
|
|
299
|
-
return result.stdout or ""
|
|
300
|
-
|
|
301
|
-
|
|
302
99
|
# --------------------------------------------------------------------------- #
|
|
303
100
|
# compose 파싱 — docker 가 정본(canonical). 손수 정규식 파서는 라벨 SSOT 를
|
|
304
101
|
# silent 하게 깨므로 쓰지 않는다(inline comment / quoted name / 4-space 들여쓰기
|
|
@@ -600,11 +397,10 @@ def provision_container_group(
|
|
|
600
397
|
*, project_root, task_key: str,
|
|
601
398
|
healthcheck_timeout_seconds: int = 120,
|
|
602
399
|
healthcheck_interval_seconds: int = 3,
|
|
603
|
-
scan_interval_seconds: float = 5.0,
|
|
604
400
|
) -> dict:
|
|
605
401
|
"""skill / bin okstra / okstra.sh 가 수렴하는 container up 단일 진입점."""
|
|
606
402
|
from . import paths
|
|
607
|
-
from .ids import compose_project_name,
|
|
403
|
+
from .ids import compose_project_name, build_container_run_trace
|
|
608
404
|
|
|
609
405
|
project_root = Path(project_root)
|
|
610
406
|
inputs = _resolve_up_inputs(project_root, task_key)
|
|
@@ -613,7 +409,7 @@ def provision_container_group(
|
|
|
613
409
|
cp = paths.container_paths(project_root, inputs["task_group"], inputs["task_id"])
|
|
614
410
|
project_name = compose_project_name(
|
|
615
411
|
inputs["project_id"], inputs["task_group"], inputs["task_id"])
|
|
616
|
-
|
|
412
|
+
run_trace = build_container_run_trace(
|
|
617
413
|
inputs["project_id"], inputs["task_group"], inputs["task_id"])
|
|
618
414
|
worktree_env, override_env = _synthesize_env_override(worktree, cp)
|
|
619
415
|
env_files = [p for p in (worktree_env, override_env) if p]
|
|
@@ -621,7 +417,7 @@ def provision_container_group(
|
|
|
621
417
|
services = resolve_compose_services(compose, env_files, str(worktree))
|
|
622
418
|
started = _compose_up(
|
|
623
419
|
cp=cp, worktree=worktree, compose=compose, project_name=project_name,
|
|
624
|
-
task_key=task_key,
|
|
420
|
+
task_key=task_key, run_trace=run_trace, services=services,
|
|
625
421
|
worktree_env=worktree_env, override_env=override_env)
|
|
626
422
|
if not started:
|
|
627
423
|
# ps 가 빈 출력(데몬 일시 오류)이거나 모든 서비스가 미생성이면 started 가
|
|
@@ -631,7 +427,7 @@ def provision_container_group(
|
|
|
631
427
|
# 가능하게 한다 — 실제 기동분을 모르니 full services 를 best-effort 로 기록.
|
|
632
428
|
_write_deploy_state(
|
|
633
429
|
cp, project_name=project_name, task_key=task_key, services=services,
|
|
634
|
-
warnings=warnings
|
|
430
|
+
warnings=warnings)
|
|
635
431
|
raise PrepareError(
|
|
636
432
|
f"container up: `up -d` 후 기동된 컨테이너가 없습니다(`docker compose -p "
|
|
637
433
|
f"{project_name} ps -a` 로 확인). compose 정의·활성 profile 을 점검한 뒤 "
|
|
@@ -645,24 +441,18 @@ def provision_container_group(
|
|
|
645
441
|
# `down --all` 스윕(deploy-state.json 있는 task 만 발견)이 회수할 수 있다.
|
|
646
442
|
_write_deploy_state(
|
|
647
443
|
cp, project_name=project_name, task_key=task_key, services=started,
|
|
648
|
-
warnings=warnings,
|
|
649
|
-
healthcheckFailed=failed)
|
|
444
|
+
warnings=warnings, healthcheckFailed=failed)
|
|
650
445
|
raise PrepareError(
|
|
651
446
|
f"container up: 다음 서비스가 시간 내 기동하지 못했습니다: {', '.join(failed)} "
|
|
652
447
|
f"(timeout={healthcheck_timeout_seconds}s). `docker compose -p "
|
|
653
448
|
f"{project_name} logs` 로 원인을 확인한 뒤 `okstra container down "
|
|
654
449
|
f"--task-key {task_key}` 로 정리하세요.")
|
|
655
|
-
#
|
|
656
|
-
#
|
|
657
|
-
# 항목이 phantom 으로 생겨 배포 수명 동안 누수된다.
|
|
658
|
-
watch = _spawn_watchers(cp=cp, worktree=worktree, project_name=project_name,
|
|
659
|
-
services=started, session_name=session_name,
|
|
660
|
-
project_root=project_root, inputs=inputs,
|
|
661
|
-
scan_interval_seconds=scan_interval_seconds)
|
|
450
|
+
# deploy-state 는 실제 기동된 started 만 기록한다 — full services 를 쓰면
|
|
451
|
+
# profiles 로 안 뜬 서비스가 `down --all` 스윕의 대상으로 남는다.
|
|
662
452
|
_write_deploy_state(cp, project_name=project_name, task_key=task_key,
|
|
663
|
-
services=started, warnings=warnings
|
|
453
|
+
services=started, warnings=warnings)
|
|
664
454
|
return {"projectName": project_name, "services": started,
|
|
665
|
-
"warnings": warnings
|
|
455
|
+
"warnings": warnings}
|
|
666
456
|
|
|
667
457
|
|
|
668
458
|
def _integrate_source_tree(inputs: dict) -> str:
|
|
@@ -679,7 +469,7 @@ def _integrate_source_tree(inputs: dict) -> str:
|
|
|
679
469
|
|
|
680
470
|
def _compose_up(
|
|
681
471
|
*, cp: dict, worktree: Path, compose: Path, project_name: str, task_key: str,
|
|
682
|
-
|
|
472
|
+
run_trace: str, services: list[str], worktree_env: str, override_env: str,
|
|
683
473
|
) -> list[str]:
|
|
684
474
|
"""label override 주입 → compose up → 실제 기동된 서비스명 반환.
|
|
685
475
|
|
|
@@ -689,9 +479,9 @@ def _compose_up(
|
|
|
689
479
|
labels = {
|
|
690
480
|
"okstra.task-key": task_key,
|
|
691
481
|
"okstra.project-name": project_name,
|
|
692
|
-
# run-trace 는 이 기능의 trace
|
|
693
|
-
#
|
|
694
|
-
"okstra.run-trace":
|
|
482
|
+
# run-trace 는 이 기능의 trace 식별자로 채워 project-name 과
|
|
483
|
+
# distinct 하게 둔다(M1).
|
|
484
|
+
"okstra.run-trace": run_trace,
|
|
695
485
|
}
|
|
696
486
|
label_path = _write_label_override(cp, services, labels)
|
|
697
487
|
argv = build_compose_up_argv(
|
|
@@ -719,117 +509,27 @@ def _started_services(project_name: str, cwd: str, services: list[str]) -> list[
|
|
|
719
509
|
return [s for s in services if s in created]
|
|
720
510
|
|
|
721
511
|
|
|
722
|
-
def _spawn_watchers(
|
|
723
|
-
*, cp: dict, worktree: Path, project_name: str, services: list[str],
|
|
724
|
-
session_name: str, project_root: Path, inputs: dict, scan_interval_seconds: float,
|
|
725
|
-
) -> dict:
|
|
726
|
-
"""tmux 가 있으면 detached 세션 + 서비스별 tail/watcher pane. 없으면 skip.
|
|
727
|
-
|
|
728
|
-
pane split 실패(pane 한도/세션 외부 종료 등)는 watcher 만 degrade 시키고 raise
|
|
729
|
-
하지 않는다 — 컨테이너는 이미 떠 있으므로 호출자가 deploy-state 를 반드시 써서
|
|
730
|
-
`down --all` 스윕에서 보이게 해야 한다."""
|
|
731
|
-
from . import tmux
|
|
732
|
-
|
|
733
|
-
if not tmux.tmux_available():
|
|
734
|
-
return {"enabled": False, "note": "감시 비활성(tmux 없음)"}
|
|
735
|
-
cp["watchers_dir"].mkdir(parents=True, exist_ok=True)
|
|
736
|
-
started: list[str] = []
|
|
737
|
-
try:
|
|
738
|
-
# holder pane 은 기본 셸로 띄워 오래 살린다(`true` 는 split 전에 세션을
|
|
739
|
-
# 무너뜨림). container 태그를 붙여 down/stop-watcher 의 스코프 reap 로
|
|
740
|
-
# tail/watcher pane 과 함께 회수되게 한다(마지막 pane 회수 시 세션 종료).
|
|
741
|
-
pane = tmux.new_detached_session(session_name, str(worktree))
|
|
742
|
-
tmux.tag_container_pane(pane, str(cp["container_dir"]))
|
|
743
|
-
for svc in services:
|
|
744
|
-
_spawn_service_panes(
|
|
745
|
-
pane=pane, cp=cp, worktree=worktree, project_name=project_name,
|
|
746
|
-
svc=svc, session_name=session_name, project_root=project_root,
|
|
747
|
-
inputs=inputs, scan_interval_seconds=scan_interval_seconds)
|
|
748
|
-
started.append(svc)
|
|
749
|
-
except (RuntimeError, OSError, subprocess.SubprocessError) as exc:
|
|
750
|
-
# watcher spawn 실패(tmux split/RuntimeError, registry flock/디스크 OSError,
|
|
751
|
-
# tmux timeout/SubprocessError)는 watcher 만 degrade 시키고 raise 하지
|
|
752
|
-
# 않는다 — 호출자가 deploy-state 를 반드시 써 컨테이너 leak 을 막는다.
|
|
753
|
-
return {"enabled": True, "session": session_name, "services": started,
|
|
754
|
-
"partial": True, "note": f"watcher 부분 실패: {exc}"}
|
|
755
|
-
return {"enabled": True, "session": session_name, "services": started}
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
def _spawn_service_panes(
|
|
759
|
-
*, pane: str, cp: dict, worktree: Path, project_name: str, svc: str,
|
|
760
|
-
session_name: str, project_root: Path, inputs: dict, scan_interval_seconds: float,
|
|
761
|
-
) -> None:
|
|
762
|
-
"""한 서비스의 tail pane + watcher loop pane 을 split 하고 registry 에 등록."""
|
|
763
|
-
from . import tmux, container_registry
|
|
764
|
-
|
|
765
|
-
findings = cp["watchers_dir"] / f"{svc}.findings.md"
|
|
766
|
-
tmux.split_container_pane(
|
|
767
|
-
session_pane=pane, cwd=str(worktree),
|
|
768
|
-
command=f"docker compose -p {project_name} logs -f {svc}",
|
|
769
|
-
title=f"tail:{svc}", scope_value=str(cp["container_dir"]), kind="tail")
|
|
770
|
-
wpane = tmux.split_container_pane(
|
|
771
|
-
session_pane=pane, cwd=str(worktree),
|
|
772
|
-
command=build_watcher_pane_command(
|
|
773
|
-
python_exe=sys.executable,
|
|
774
|
-
module_root=str(Path(__file__).resolve().parent.parent),
|
|
775
|
-
project_name=project_name, service=svc, findings_path=str(findings),
|
|
776
|
-
scan_interval_seconds=scan_interval_seconds),
|
|
777
|
-
title=f"watch:{svc}", scope_value=str(cp["container_dir"]), kind="watcher")
|
|
778
|
-
container_registry.reserve(
|
|
779
|
-
project_root, inputs["task_group"], inputs["task_id"], svc,
|
|
780
|
-
session_name=session_name, pane_id=wpane or "", findings_path=str(findings))
|
|
781
|
-
|
|
782
|
-
|
|
783
512
|
def _write_deploy_state(cp: dict, **state) -> None:
|
|
784
513
|
cp["container_dir"].mkdir(parents=True, exist_ok=True)
|
|
785
514
|
write_owned_object_atomic(cp["deploy_state"], state, artifact="container deploy state")
|
|
786
515
|
|
|
787
516
|
|
|
788
517
|
# --------------------------------------------------------------------------- #
|
|
789
|
-
# status /
|
|
518
|
+
# status / down
|
|
790
519
|
# --------------------------------------------------------------------------- #
|
|
791
520
|
|
|
792
521
|
def status_container_group(*, project_root, task_key: str) -> dict:
|
|
793
|
-
"""라벨 쿼리
|
|
794
|
-
from . import container_registry
|
|
522
|
+
"""라벨 쿼리 — 컨테이너 존재 여부의 기준값(SSOT)."""
|
|
795
523
|
from .ids import compose_project_name
|
|
796
524
|
|
|
797
525
|
project_id, group, task_id = _split_task_key(task_key)
|
|
798
526
|
project_name = compose_project_name(project_id, group, task_id)
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
return {"projectName": project_name, "containers": containers,
|
|
802
|
-
"watchers": registry.get("services", {})}
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
def logs_container_group(*, project_root, task_key: str, service: str = "") -> dict:
|
|
806
|
-
"""deploy-state + watcher findings 경로를 안내(로그 자체는 tmux pane 에 있음)."""
|
|
807
|
-
from . import paths, container_registry
|
|
808
|
-
|
|
809
|
-
project_id, group, task_id = _split_task_key(task_key)
|
|
810
|
-
cp = paths.container_paths(Path(project_root), group, task_id)
|
|
811
|
-
services = container_registry.lookup(Path(project_root), group, task_id).get(
|
|
812
|
-
"services", {})
|
|
813
|
-
if service:
|
|
814
|
-
services = {service: services[service]} if service in services else {}
|
|
815
|
-
return {"watchersDir": str(cp["watchers_dir"]), "watchers": services}
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
def stop_watcher_group(*, project_root, task_key: str) -> dict:
|
|
819
|
-
"""watcher/tail pane(@okstra_container_run) 만 회수하고 컨테이너는 살려둔다."""
|
|
820
|
-
project_id, group, task_id = _split_task_key(task_key)
|
|
821
|
-
reaped = _reap_container_panes(Path(project_root), group, task_id)
|
|
822
|
-
return {"reapedPanes": reaped, "note": "컨테이너는 유지됨(감시만 종료)"}
|
|
527
|
+
return {"projectName": project_name,
|
|
528
|
+
"containers": query_containers_by_label(project_name)}
|
|
823
529
|
|
|
824
530
|
|
|
825
531
|
def down_container_group(*, project_root, task_key: str = "", all_groups: bool = False) -> dict:
|
|
826
|
-
"""라벨 쿼리로
|
|
827
|
-
|
|
828
|
-
registry 는 보조 인덱스라 누락돼도 'watcher 없음'을 의미하지 않는다 — tmux 를
|
|
829
|
-
직접 스캔(@okstra_container_run)해 orphan pane 까지 reap 한다(spec §11).
|
|
830
|
-
pane 회수는 항상 현재 project-root 스코프로 한정한다 — 동시 세션이 흔한
|
|
831
|
-
레포라 다른 task/프로젝트의 watcher pane 을 절대 죽이지 않는다(I2)."""
|
|
832
|
-
from . import paths
|
|
532
|
+
"""라벨 쿼리로 컨테이너를 down 한다."""
|
|
833
533
|
from .ids import compose_project_name
|
|
834
534
|
|
|
835
535
|
project_root = Path(project_root)
|
|
@@ -843,19 +543,8 @@ def down_container_group(*, project_root, task_key: str = "", all_groups: bool =
|
|
|
843
543
|
# 영속 데이터를 파괴하지 않는다.
|
|
844
544
|
_docker_run(["docker", "compose", "-p", project_name, "down",
|
|
845
545
|
"--remove-orphans"])
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
# registry drift 로 등록 안 된 orphan pane 회수는 `--all`(이 프로젝트 전체)
|
|
849
|
-
# 때만, 그것도 **이 project-root 의 .okstra/ prefix** 로만 스코프한다. 단일
|
|
850
|
-
# down 은 위 per-task 회수로 끝낸다(글로벌 sweep 금지 — 동시 세션 간섭 차단).
|
|
851
|
-
orphans = _reap_project_orphan_panes(project_root) if all_groups else []
|
|
852
|
-
return {"downed": results, "orphanPanesReaped": orphans}
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
def _reap_project_orphan_panes(project_root: Path) -> list[str]:
|
|
856
|
-
prefix = str((project_root / ".okstra").resolve())
|
|
857
|
-
return _kill_panes_by_tag(
|
|
858
|
-
lambda tag: tag == prefix or tag.startswith(prefix + os.sep))
|
|
546
|
+
results.append({"projectName": project_name})
|
|
547
|
+
return {"downed": results}
|
|
859
548
|
|
|
860
549
|
|
|
861
550
|
def _down_targets(project_root, task_key: str, all_groups: bool) -> list[tuple[str, str, str]]:
|
|
@@ -893,66 +582,33 @@ def _read_project_id(project_json: Path) -> str:
|
|
|
893
582
|
return ""
|
|
894
583
|
|
|
895
584
|
|
|
896
|
-
def _reap_container_panes(project_root: Path, group: str, task_id: str) -> list[str]:
|
|
897
|
-
"""이 task 의 container_dir 태그를 가진 pane 만 회수한다(task 스코프)."""
|
|
898
|
-
from . import paths, container_registry
|
|
899
|
-
cp = paths.container_paths(project_root, group, task_id)
|
|
900
|
-
container_dir = str(cp["container_dir"].resolve())
|
|
901
|
-
container_registry.clear(project_root, group, task_id)
|
|
902
|
-
# tail/watcher pane 은 container_dir 로, analyze pane 은 그 하위 findings 경로로
|
|
903
|
-
# 태그된다 — 둘 다 회수하되 prefix 가 task 의 container_dir 라 스코프는 유지된다.
|
|
904
|
-
return _kill_panes_by_tag(
|
|
905
|
-
lambda tag: tag == container_dir or tag.startswith(container_dir + os.sep))
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
def _kill_panes_by_tag(match: Callable[[str], bool]) -> list[str]:
|
|
909
|
-
"""`@okstra_container_run` 태그값이 ``match`` 를 통과하는 pane 만 tmux 로 kill.
|
|
910
|
-
|
|
911
|
-
태그값은 해당 task 의 container_dir 절대경로다. 무조건 글로벌 sweep 은 동시
|
|
912
|
-
세션 간섭을 일으키므로 호출자가 항상 스코프 predicate 를 준다(I2)."""
|
|
913
|
-
from . import tmux
|
|
914
|
-
if not tmux.tmux_available():
|
|
915
|
-
return []
|
|
916
|
-
result = tmux.run_tmux(
|
|
917
|
-
["list-panes", "-a", "-F", "#{pane_id}\t#{@okstra_container_run}"])
|
|
918
|
-
if result.returncode != 0:
|
|
919
|
-
return []
|
|
920
|
-
killed: list[str] = []
|
|
921
|
-
for line in result.stdout.splitlines():
|
|
922
|
-
parts = line.split("\t")
|
|
923
|
-
if len(parts) == 2 and parts[0] and parts[1] and match(parts[1]):
|
|
924
|
-
tmux.kill_pane(parts[0])
|
|
925
|
-
killed.append(parts[0])
|
|
926
|
-
return killed
|
|
927
|
-
|
|
928
|
-
|
|
929
585
|
# --------------------------------------------------------------------------- #
|
|
930
586
|
# argparse 디스패치(recap.py 패턴)
|
|
931
587
|
# --------------------------------------------------------------------------- #
|
|
932
588
|
|
|
589
|
+
_CLI_EPILOG = r"""Usage:
|
|
590
|
+
okstra container up [options]
|
|
591
|
+
okstra container status [options]
|
|
592
|
+
okstra container down [options]
|
|
593
|
+
"""
|
|
594
|
+
_CLI_DESCRIPTION = "Manage the okstra container runtime."
|
|
595
|
+
|
|
596
|
+
|
|
933
597
|
def main(argv: list[str] | None = None) -> int:
|
|
934
|
-
parser = argparse.ArgumentParser(
|
|
598
|
+
parser = argparse.ArgumentParser(
|
|
599
|
+
description=_CLI_DESCRIPTION,
|
|
600
|
+
epilog=_CLI_EPILOG,
|
|
601
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
602
|
+
prog="okstra container")
|
|
935
603
|
sub = parser.add_subparsers(dest="command", required=True)
|
|
936
|
-
for name in ("up", "status", "
|
|
604
|
+
for name in ("up", "status", "down"):
|
|
937
605
|
sp = sub.add_parser(name)
|
|
938
606
|
sp.add_argument("--project-root", required=True)
|
|
939
607
|
sp.add_argument("--task-key", default="")
|
|
940
608
|
sp.add_argument("--text", action="store_true")
|
|
941
609
|
if name == "down":
|
|
942
610
|
sp.add_argument("--all", action="store_true")
|
|
943
|
-
if name == "logs":
|
|
944
|
-
sp.add_argument("--service", default="")
|
|
945
|
-
watch = sub.add_parser("watch") # watcher pane 내부에서 도는 장기 루프
|
|
946
|
-
watch.add_argument("--project-name", required=True)
|
|
947
|
-
watch.add_argument("--service", required=True)
|
|
948
|
-
watch.add_argument("--findings", required=True)
|
|
949
|
-
watch.add_argument("--scan-interval", type=float, default=5.0)
|
|
950
611
|
args = parser.parse_args(argv)
|
|
951
|
-
if args.command == "watch":
|
|
952
|
-
run_service_watcher(
|
|
953
|
-
project_name=args.project_name, service=args.service,
|
|
954
|
-
findings_path=args.findings, scan_interval_seconds=args.scan_interval)
|
|
955
|
-
return 0
|
|
956
612
|
result = _dispatch(args)
|
|
957
613
|
print(
|
|
958
614
|
render_container_text(args.command, result)
|
|
@@ -964,21 +620,16 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
964
620
|
|
|
965
621
|
def render_container_text(command: str, payload: dict) -> str:
|
|
966
622
|
"""container 명령별 승인된 상태만 고정 순서로 투영한다."""
|
|
967
|
-
if command not in {"up", "status", "
|
|
623
|
+
if command not in {"up", "status", "down"}:
|
|
968
624
|
raise ValueError("unknown container text purpose")
|
|
969
625
|
rows = [f"Okstra container {command}\n", line("Status", "ready")]
|
|
970
|
-
for label, key in (("Project name", "projectName"), ("
|
|
971
|
-
("Note", "note")):
|
|
626
|
+
for label, key in (("Project name", "projectName"), ("Note", "note")):
|
|
972
627
|
if key in payload:
|
|
973
628
|
rows.append(line(label, payload.get(key)))
|
|
974
629
|
if command == "up":
|
|
975
630
|
rows.extend(_render_container_up(payload))
|
|
976
|
-
elif command
|
|
977
|
-
rows.extend(
|
|
978
|
-
if command == "status":
|
|
979
|
-
rows.extend(_render_container_status(payload))
|
|
980
|
-
elif command == "stop-watcher":
|
|
981
|
-
rows.extend(_render_reaped_panes("Reaped pane", payload.get("reapedPanes")))
|
|
631
|
+
elif command == "status":
|
|
632
|
+
rows.extend(_render_container_status(payload))
|
|
982
633
|
else:
|
|
983
634
|
rows.extend(_render_container_down(payload))
|
|
984
635
|
return "".join(rows)
|
|
@@ -990,26 +641,6 @@ def _render_container_up(payload: dict) -> list[str]:
|
|
|
990
641
|
rows.append(line(f"Service {index} name", service))
|
|
991
642
|
for index, warning in enumerate(payload.get("warnings") or [], 1):
|
|
992
643
|
rows.append(line(f"Warning {index} text", warning))
|
|
993
|
-
watch = payload.get("watch") if isinstance(payload.get("watch"), dict) else {}
|
|
994
|
-
for label, key in (("Watcher enabled", "enabled"), ("Watcher session", "session"),
|
|
995
|
-
("Watcher note", "note")):
|
|
996
|
-
if key in watch:
|
|
997
|
-
rows.append(line(label, watch.get(key)))
|
|
998
|
-
for index, service in enumerate(watch.get("services") or [], 1):
|
|
999
|
-
rows.append(line(f"Watcher service {index} name", service))
|
|
1000
|
-
return rows
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
def _render_container_watchers(payload: dict) -> list[str]:
|
|
1004
|
-
watchers = payload.get("watchers") if isinstance(payload.get("watchers"), dict) else {}
|
|
1005
|
-
rows: list[str] = []
|
|
1006
|
-
for index, (service, metadata) in enumerate(sorted(watchers.items()), 1):
|
|
1007
|
-
rows.append(line(f"Watcher {index} service", service))
|
|
1008
|
-
values = metadata if isinstance(metadata, dict) else {}
|
|
1009
|
-
for label, key in (("pane ID", "paneId"), ("session", "sessionName"),
|
|
1010
|
-
("findings path", "findingsPath")):
|
|
1011
|
-
if key in values:
|
|
1012
|
-
rows.append(line(f"Watcher {index} {label}", values.get(key)))
|
|
1013
644
|
return rows
|
|
1014
645
|
|
|
1015
646
|
|
|
@@ -1025,21 +656,12 @@ def _render_container_status(payload: dict) -> list[str]:
|
|
|
1025
656
|
return rows
|
|
1026
657
|
|
|
1027
658
|
|
|
1028
|
-
def _render_reaped_panes(prefix: str, raw_panes: object) -> list[str]:
|
|
1029
|
-
panes = raw_panes if isinstance(raw_panes, list) else []
|
|
1030
|
-
return [line(f"{prefix} {index}", pane) for index, pane in enumerate(panes, 1)]
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
659
|
def _render_container_down(payload: dict) -> list[str]:
|
|
1034
660
|
downed = payload.get("downed") if isinstance(payload.get("downed"), list) else []
|
|
1035
661
|
rows: list[str] = []
|
|
1036
662
|
for index, item in enumerate(downed, 1):
|
|
1037
663
|
values = item if isinstance(item, dict) else {}
|
|
1038
664
|
rows.append(line(f"Downed {index} project name", values.get("projectName")))
|
|
1039
|
-
rows.extend(_render_reaped_panes(
|
|
1040
|
-
f"Downed {index} reaped pane", values.get("reapedPanes")
|
|
1041
|
-
))
|
|
1042
|
-
rows.extend(_render_reaped_panes("Orphan pane", payload.get("orphanPanesReaped")))
|
|
1043
665
|
return rows
|
|
1044
666
|
|
|
1045
667
|
|
|
@@ -1049,11 +671,6 @@ def _dispatch(args) -> dict:
|
|
|
1049
671
|
return provision_container_group(project_root=pr, task_key=args.task_key)
|
|
1050
672
|
if args.command == "status":
|
|
1051
673
|
return status_container_group(project_root=pr, task_key=args.task_key)
|
|
1052
|
-
if args.command == "logs":
|
|
1053
|
-
return logs_container_group(
|
|
1054
|
-
project_root=pr, task_key=args.task_key, service=args.service)
|
|
1055
|
-
if args.command == "stop-watcher":
|
|
1056
|
-
return stop_watcher_group(project_root=pr, task_key=args.task_key)
|
|
1057
674
|
return down_container_group(
|
|
1058
675
|
project_root=pr, task_key=args.task_key, all_groups=args.all)
|
|
1059
676
|
|
|
@@ -15,7 +15,7 @@ from typing import Iterable
|
|
|
15
15
|
|
|
16
16
|
from okstra_ctl.paths import RunRef, runs_dir_of, task_manifest_file
|
|
17
17
|
from okstra_ctl.task_target import resolve_task_root, project_rel
|
|
18
|
-
from okstra_ctl.json_boundary import load_owned_object
|
|
18
|
+
from okstra_ctl.json_boundary import JsonBoundaryError, load_owned_object
|
|
19
19
|
|
|
20
20
|
|
|
21
21
|
INPUT_FILES = (
|
|
@@ -85,7 +85,7 @@ def _load_json(path: Path) -> dict:
|
|
|
85
85
|
return {}
|
|
86
86
|
try:
|
|
87
87
|
data = load_owned_object(path, artifact="context cost input")
|
|
88
|
-
except
|
|
88
|
+
except JsonBoundaryError:
|
|
89
89
|
return {}
|
|
90
90
|
return data if isinstance(data, dict) else {}
|
|
91
91
|
|
|
@@ -481,8 +481,22 @@ def analyze_task_bundle(task_root: Path, project_root: Path) -> dict:
|
|
|
481
481
|
}
|
|
482
482
|
|
|
483
483
|
|
|
484
|
+
_CLI_EPILOG = r"""Usage:
|
|
485
|
+
okstra context-cost <task-root>
|
|
486
|
+
okstra context-cost <task-key> --project-root <dir>
|
|
487
|
+
okstra context-cost <task-key> --cwd <dir>
|
|
488
|
+
|
|
489
|
+
Output: JSON with task file counts, current-run counts, lead Phase 1 read
|
|
490
|
+
surface, analysis-worker initial read surface, and report-writer synthesis
|
|
491
|
+
surface. This is read-only; it never mutates task artifacts.
|
|
492
|
+
"""
|
|
493
|
+
|
|
494
|
+
|
|
484
495
|
def main(argv: list[str] | None = None) -> int:
|
|
485
496
|
parser = argparse.ArgumentParser(
|
|
497
|
+
prog="okstra context-cost",
|
|
498
|
+
epilog=_CLI_EPILOG,
|
|
499
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
486
500
|
description="Estimate context/file-read cost for an okstra task bundle."
|
|
487
501
|
)
|
|
488
502
|
parser.add_argument("target", help="task root path or task-key")
|