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
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
"""Per-task container registry — flock 보조 인덱스.
|
|
2
|
-
|
|
3
|
-
docker 라벨은 컨테이너 존재 여부의 기준값(SSOT)이지만 tmux session/pane id 나
|
|
4
|
-
findings 파일 경로 같은 운영 메타데이터는 추적하지 못한다. 이 모듈은 task root
|
|
5
|
-
하위 ``container/registry.json`` 에 그런 보조 정보를 가벼운 dict 로 보관한다.
|
|
6
|
-
|
|
7
|
-
flock + tmp+``os.replace`` 원자 쓰기 idiom 은 ``json_registry`` 의 공용
|
|
8
|
-
Interface 를 사용하고, lock/registry 경로만 ``paths.container_paths()`` 로
|
|
9
|
-
해소해 전역 ``~/.okstra/worktrees/registry.json`` 과 분리한다. dict 구조는
|
|
10
|
-
``{"services": {<svc>: {session_name, pane_id, findings_path}}}`` 한 가지뿐 —
|
|
11
|
-
worktree_registry 의 dataclass 는 복제하지 않는다.
|
|
12
|
-
"""
|
|
13
|
-
from __future__ import annotations
|
|
14
|
-
|
|
15
|
-
from pathlib import Path
|
|
16
|
-
|
|
17
|
-
from .json_registry import load_registry_json, registry_lock, save_registry_json
|
|
18
|
-
from . import paths
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
def _registry_lock(lock_path: Path):
|
|
22
|
-
"""``container/registry.json.lock`` 에 대한 배타 flock."""
|
|
23
|
-
return registry_lock(lock_path)
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
def _load(registry_path: Path) -> dict:
|
|
27
|
-
data = load_registry_json(registry_path, lambda: {"services": {}})
|
|
28
|
-
data.setdefault("services", {})
|
|
29
|
-
return data
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
def _save(registry_path: Path, data: dict) -> None:
|
|
33
|
-
save_registry_json(registry_path, data)
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
def reserve(
|
|
37
|
-
project_root: Path, group: str, task_id: str, service: str,
|
|
38
|
-
*, session_name: str, pane_id: str, findings_path: str,
|
|
39
|
-
) -> None:
|
|
40
|
-
"""``service`` 의 tmux session/pane/findings 메타데이터를 등록한다."""
|
|
41
|
-
cp = paths.container_paths(project_root, group, task_id)
|
|
42
|
-
with _registry_lock(cp["registry_lock"]):
|
|
43
|
-
data = _load(cp["registry"])
|
|
44
|
-
data["services"][service] = {
|
|
45
|
-
"session_name": session_name,
|
|
46
|
-
"pane_id": pane_id,
|
|
47
|
-
"findings_path": findings_path,
|
|
48
|
-
}
|
|
49
|
-
_save(cp["registry"], data)
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
def lookup(project_root: Path, group: str, task_id: str) -> dict:
|
|
53
|
-
"""현재 registry dict 를 돌려준다. 파일 부재 시 ``{"services": {}}``."""
|
|
54
|
-
cp = paths.container_paths(project_root, group, task_id)
|
|
55
|
-
with _registry_lock(cp["registry_lock"]):
|
|
56
|
-
return _load(cp["registry"])
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
def release(project_root: Path, group: str, task_id: str, service: str) -> None:
|
|
60
|
-
"""``service`` 항목 하나만 제거한다(없으면 no-op)."""
|
|
61
|
-
cp = paths.container_paths(project_root, group, task_id)
|
|
62
|
-
with _registry_lock(cp["registry_lock"]):
|
|
63
|
-
data = _load(cp["registry"])
|
|
64
|
-
data["services"].pop(service, None)
|
|
65
|
-
_save(cp["registry"], data)
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
def clear(project_root: Path, group: str, task_id: str) -> None:
|
|
69
|
-
"""모든 service 항목을 비운다."""
|
|
70
|
-
cp = paths.container_paths(project_root, group, task_id)
|
|
71
|
-
with _registry_lock(cp["registry_lock"]):
|
|
72
|
-
_save(cp["registry"], {"services": {}})
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
"""Assign improvement-discovery primary passes from the resolved roster."""
|
|
2
|
-
from __future__ import annotations
|
|
3
|
-
|
|
4
|
-
from collections.abc import Mapping
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
def assign_primary_lenses(
|
|
8
|
-
worker_ids: list[str],
|
|
9
|
-
priority_lenses: list[str],
|
|
10
|
-
) -> dict[str, str]:
|
|
11
|
-
"""Round-robin resolved worker instances over resolved priority lenses."""
|
|
12
|
-
if not worker_ids:
|
|
13
|
-
raise ValueError("worker IDs must not be empty")
|
|
14
|
-
if not priority_lenses:
|
|
15
|
-
raise ValueError("priority lenses must not be empty")
|
|
16
|
-
if len(set(worker_ids)) != len(worker_ids):
|
|
17
|
-
raise ValueError("worker IDs must be unique")
|
|
18
|
-
return {
|
|
19
|
-
worker_id: priority_lenses[index % len(priority_lenses)]
|
|
20
|
-
for index, worker_id in enumerate(worker_ids)
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
def validate_primary_lens_assignments(
|
|
25
|
-
assignments: Mapping[str, str],
|
|
26
|
-
worker_ids: list[str],
|
|
27
|
-
priority_lenses: list[str],
|
|
28
|
-
) -> list[str]:
|
|
29
|
-
"""Require every selected analyser once at its roster-order lens."""
|
|
30
|
-
errors: list[str] = []
|
|
31
|
-
if not worker_ids:
|
|
32
|
-
return ["resolved analyser worker IDs must not be empty"]
|
|
33
|
-
if not priority_lenses:
|
|
34
|
-
return ["resolved priority lenses must not be empty"]
|
|
35
|
-
try:
|
|
36
|
-
expected = assign_primary_lenses(worker_ids, priority_lenses)
|
|
37
|
-
except ValueError as exc:
|
|
38
|
-
return [str(exc)]
|
|
39
|
-
if list(assignments) != worker_ids:
|
|
40
|
-
errors.append(
|
|
41
|
-
"assignment order must match resolved analyser worker order: "
|
|
42
|
-
+ ", ".join(worker_ids)
|
|
43
|
-
)
|
|
44
|
-
for worker_id in worker_ids:
|
|
45
|
-
if worker_id not in assignments:
|
|
46
|
-
errors.append(f"missing worker assignment: {worker_id}")
|
|
47
|
-
for worker_id, lens in assignments.items():
|
|
48
|
-
if worker_id not in expected:
|
|
49
|
-
errors.append(f"extra worker assignment: {worker_id}")
|
|
50
|
-
continue
|
|
51
|
-
if lens not in priority_lenses:
|
|
52
|
-
errors.append(
|
|
53
|
-
f"{worker_id}: primary lens {lens!r} is outside resolved priority lenses"
|
|
54
|
-
)
|
|
55
|
-
continue
|
|
56
|
-
if lens != expected[worker_id]:
|
|
57
|
-
errors.append(
|
|
58
|
-
f"{worker_id}: expected primary lens {expected[worker_id]!r}, "
|
|
59
|
-
f"got {lens!r}"
|
|
60
|
-
)
|
|
61
|
-
return errors
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
"""run id 해석."""
|
|
2
|
-
from __future__ import annotations
|
|
3
|
-
|
|
4
|
-
from pathlib import Path
|
|
5
|
-
from typing import List
|
|
6
|
-
|
|
7
|
-
from .listing import list_runs
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class ResolveError(Exception):
|
|
11
|
-
"""runId 해석 실패. candidates 에 후보 목록 포함."""
|
|
12
|
-
|
|
13
|
-
def __init__(self, message: str, candidates: List[str]) -> None:
|
|
14
|
-
super().__init__(message)
|
|
15
|
-
self.candidates = candidates
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
def _all_run_ids(home: Path) -> List[str]:
|
|
19
|
-
rows = list_runs(home, include_archive=True)
|
|
20
|
-
return [r["runId"] for r in rows]
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
def resolve_run_id(home: Path, query: str) -> str:
|
|
24
|
-
"""prefix substring 매칭 규칙으로 runId 해석.
|
|
25
|
-
1. 정확 일치 → 즉시 반환.
|
|
26
|
-
2. endswith 일치 1건 → 반환.
|
|
27
|
-
3. substring 일치 1건 → 반환.
|
|
28
|
-
그 외(0건/다수) → ResolveError.
|
|
29
|
-
"""
|
|
30
|
-
ids = _all_run_ids(home)
|
|
31
|
-
if query in ids:
|
|
32
|
-
return query
|
|
33
|
-
ends = [i for i in ids if i.endswith(query)]
|
|
34
|
-
if len(ends) == 1:
|
|
35
|
-
return ends[0]
|
|
36
|
-
if len(ends) > 1:
|
|
37
|
-
raise ResolveError(f"ambiguous (endswith): {query!r}", ends)
|
|
38
|
-
subs = [i for i in ids if query in i]
|
|
39
|
-
if len(subs) == 1:
|
|
40
|
-
return subs[0]
|
|
41
|
-
if len(subs) > 1:
|
|
42
|
-
raise ResolveError(f"ambiguous (substring): {query!r}", subs)
|
|
43
|
-
raise ResolveError(f"no run matches {query!r}", [])
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
def resolve_last(home: Path, *, project: str, task_group: str) -> str:
|
|
47
|
-
"""가장 최근 runId 를 반환. 매칭 없으면 ResolveError."""
|
|
48
|
-
rows = list_runs(home, project=project, task_group=task_group,
|
|
49
|
-
include_archive=True, limit=1)
|
|
50
|
-
if not rows:
|
|
51
|
-
raise ResolveError(
|
|
52
|
-
f"no run found for project={project} task_group={task_group}", []
|
|
53
|
-
)
|
|
54
|
-
return rows[0]["runId"]
|
|
@@ -1,275 +0,0 @@
|
|
|
1
|
-
"""Stale team-member reconciliation for run-end teardown.
|
|
2
|
-
|
|
3
|
-
A Claude Code team member clears its own ``isActive`` flag in
|
|
4
|
-
``~/.claude/teams/<team>/config.json`` when its ``Agent()`` dispatch returns, so
|
|
5
|
-
by Phase 7 every worker is normally already inactive. The one failure mode is a
|
|
6
|
-
member whose tmux pane died WITHOUT clearing the flag (killed mid-turn): it stays
|
|
7
|
-
``isActive: true`` forever and keeps showing as a live member that re-sending
|
|
8
|
-
``shutdown_request`` cannot clear — the addressee is already gone.
|
|
9
|
-
|
|
10
|
-
This module reconciles exactly that case: a member with ``isActive`` truthy
|
|
11
|
-
whose recorded tmux pane is no longer live is flipped to inactive. It NEVER
|
|
12
|
-
touches a member whose pane is still live, the lead, or a member with no
|
|
13
|
-
recorded pane — those are left for graceful shutdown.
|
|
14
|
-
|
|
15
|
-
CC v2.1.178 removed ``TeamDelete``: the implicit team ends with the session and
|
|
16
|
-
run-end teardown only dismisses teammates via ``shutdown_request``. Reconcile
|
|
17
|
-
keeps the roster honest so a dismissed-but-dead member does not linger as a
|
|
18
|
-
live pill — see okstra-lead-contract.md "Run-scoped worker-resource lifecycle"
|
|
19
|
-
for neutral cleanup ownership and the selected Claude adapter's
|
|
20
|
-
"Run-scoped resource lifecycle" for this provider-specific reconciliation.
|
|
21
|
-
|
|
22
|
-
Roster resolution (``--project-root``) exists because ``team-state.lead.sessionId``
|
|
23
|
-
is a phase-3 snapshot: Claude Code re-issues the session id across resume /
|
|
24
|
-
compaction mid-run, so the live on-disk roster often sits under a DIFFERENT
|
|
25
|
-
``session-*`` dir than the snapshot names. Resolving by the CURRENT live session
|
|
26
|
-
(the newest jsonl under the encoded-cwd projects dir) is what makes split-pane
|
|
27
|
-
teardown find the teammates it must dismiss.
|
|
28
|
-
"""
|
|
29
|
-
from __future__ import annotations
|
|
30
|
-
|
|
31
|
-
import json
|
|
32
|
-
import os
|
|
33
|
-
import subprocess
|
|
34
|
-
import sys
|
|
35
|
-
import tempfile
|
|
36
|
-
from pathlib import Path
|
|
37
|
-
|
|
38
|
-
from okstra_ctl.session import resolve_inproc_lead_session_id
|
|
39
|
-
from okstra_ctl.json_boundary import (
|
|
40
|
-
JsonBoundaryError,
|
|
41
|
-
external_claude_team_json_source,
|
|
42
|
-
load_external_json,
|
|
43
|
-
)
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
def live_pane_ids() -> tuple[bool, set[str]]:
|
|
47
|
-
"""Return ``(tmux_available, live_pane_ids)``.
|
|
48
|
-
|
|
49
|
-
``tmux_available`` is False when no tmux server is reachable; the caller must
|
|
50
|
-
then skip reconciliation rather than treat every pane as dead.
|
|
51
|
-
"""
|
|
52
|
-
try:
|
|
53
|
-
out = subprocess.run(
|
|
54
|
-
["tmux", "list-panes", "-a", "-F", "#{pane_id}"],
|
|
55
|
-
capture_output=True, text=True,
|
|
56
|
-
)
|
|
57
|
-
except FileNotFoundError:
|
|
58
|
-
return False, set()
|
|
59
|
-
if out.returncode != 0:
|
|
60
|
-
return False, set()
|
|
61
|
-
return True, {ln.strip() for ln in out.stdout.splitlines() if ln.strip()}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
def reconcile_members(config: dict, live_panes: set[str]) -> list[str]:
|
|
65
|
-
"""Flip dead-pane stale-active members to inactive in-place.
|
|
66
|
-
|
|
67
|
-
Returns the names of members that were flipped. A member qualifies only when
|
|
68
|
-
it is not the lead, ``isActive`` is truthy, it has a recorded ``tmuxPaneId``,
|
|
69
|
-
and that pane is not among ``live_panes``.
|
|
70
|
-
"""
|
|
71
|
-
lead = config.get("leadAgentId")
|
|
72
|
-
flipped: list[str] = []
|
|
73
|
-
for member in config.get("members", []):
|
|
74
|
-
if member.get("agentId") == lead:
|
|
75
|
-
continue
|
|
76
|
-
pane = (member.get("tmuxPaneId") or "").strip()
|
|
77
|
-
if not member.get("isActive") or not pane:
|
|
78
|
-
continue
|
|
79
|
-
if pane not in live_panes:
|
|
80
|
-
member["isActive"] = False
|
|
81
|
-
flipped.append(member.get("name", member.get("agentId", "?")))
|
|
82
|
-
return flipped
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
def dismissible_members(config: dict) -> list[str]:
|
|
86
|
-
"""Names of every non-lead member — the set the lead may ``shutdown_request``.
|
|
87
|
-
|
|
88
|
-
The lead further filters by confirmed-complete (result path present) before
|
|
89
|
-
dismissing; this just tells it which teammates the resolved roster holds.
|
|
90
|
-
"""
|
|
91
|
-
lead = config.get("leadAgentId")
|
|
92
|
-
names: list[str] = []
|
|
93
|
-
for member in config.get("members", []):
|
|
94
|
-
if member.get("agentId") == lead:
|
|
95
|
-
continue
|
|
96
|
-
names.append(member.get("name", member.get("agentId", "?")))
|
|
97
|
-
return names
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
def _team_config_for_session(session_id: str) -> Path:
|
|
101
|
-
"""``~/.claude/teams/session-<prefix>/config.json`` for a session id.
|
|
102
|
-
|
|
103
|
-
The harness names the team dir with the session id's first segment (the
|
|
104
|
-
8-hex stem before the first dash), never the ``teamName`` label.
|
|
105
|
-
"""
|
|
106
|
-
prefix = session_id.split("-", 1)[0]
|
|
107
|
-
return Path.home() / ".claude" / "teams" / f"session-{prefix}" / "config.json"
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
def _live_team_config(project_root: Path) -> Path | None:
|
|
111
|
-
"""Resolve the CURRENT live session's team config, or None when absent.
|
|
112
|
-
|
|
113
|
-
Robust to the mid-run session-id drift that strands the phase-3
|
|
114
|
-
``lead.sessionId`` snapshot: keys off the live session, not the snapshot.
|
|
115
|
-
"""
|
|
116
|
-
sid = resolve_inproc_lead_session_id(project_root)
|
|
117
|
-
if not sid:
|
|
118
|
-
return None
|
|
119
|
-
cand = _team_config_for_session(sid)
|
|
120
|
-
return cand if cand.is_file() else None
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
def _config_path(team_arg: str) -> Path:
|
|
124
|
-
"""Resolve a team name, a team dir, or a config.json path to the config file."""
|
|
125
|
-
p = Path(team_arg)
|
|
126
|
-
if p.name == "config.json":
|
|
127
|
-
return p
|
|
128
|
-
if p.is_dir():
|
|
129
|
-
return p / "config.json"
|
|
130
|
-
return Path.home() / ".claude" / "teams" / team_arg / "config.json"
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
def _atomic_write(path: Path, text: str) -> None:
|
|
134
|
-
"""Replace ``path`` atomically — config.json is the harness's roster source
|
|
135
|
-
of truth, so a partial write on crash must never be observable."""
|
|
136
|
-
fd, tmp = tempfile.mkstemp(dir=str(path.parent), prefix=".reconcile-")
|
|
137
|
-
try:
|
|
138
|
-
with os.fdopen(fd, "w") as handle:
|
|
139
|
-
handle.write(text)
|
|
140
|
-
os.replace(tmp, path)
|
|
141
|
-
except BaseException:
|
|
142
|
-
try:
|
|
143
|
-
os.unlink(tmp)
|
|
144
|
-
except OSError:
|
|
145
|
-
pass
|
|
146
|
-
raise
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
def _reconcile_config(cfg_path: Path, dry_run: bool, print_members: bool) -> int:
|
|
150
|
-
"""Read, reconcile, and (unless dry-run) write back one config.json.
|
|
151
|
-
|
|
152
|
-
When ``print_members`` is set, also emit one ``dismissible-member: <name>``
|
|
153
|
-
line per non-lead member so the lead knows whom to ``shutdown_request``.
|
|
154
|
-
"""
|
|
155
|
-
try:
|
|
156
|
-
# 외부 입력: Claude Code가 소유하는 team roster 설정이다.
|
|
157
|
-
config = load_external_json(
|
|
158
|
-
external_claude_team_json_source(cfg_path),
|
|
159
|
-
artifact="Claude team configuration",
|
|
160
|
-
)
|
|
161
|
-
except JsonBoundaryError as exc:
|
|
162
|
-
print(f"team-reconcile: cannot read team config at {cfg_path}: {exc}",
|
|
163
|
-
file=sys.stderr)
|
|
164
|
-
return 1
|
|
165
|
-
|
|
166
|
-
available, live = live_pane_ids()
|
|
167
|
-
if not available:
|
|
168
|
-
print("team-reconcile: tmux unavailable — skipped (no flip)")
|
|
169
|
-
else:
|
|
170
|
-
flipped = reconcile_members(config, live)
|
|
171
|
-
if flipped and dry_run:
|
|
172
|
-
print("team-reconcile (dry-run): would deactivate " + ", ".join(flipped))
|
|
173
|
-
elif flipped:
|
|
174
|
-
_atomic_write(cfg_path, json.dumps(config, indent=2, ensure_ascii=False) + "\n")
|
|
175
|
-
print(f"team-reconcile: deactivated {len(flipped)} stale member(s): "
|
|
176
|
-
+ ", ".join(flipped))
|
|
177
|
-
else:
|
|
178
|
-
print("team-reconcile: no stale-active members")
|
|
179
|
-
|
|
180
|
-
if print_members:
|
|
181
|
-
for name in dismissible_members(config):
|
|
182
|
-
print(f"dismissible-member: {name}")
|
|
183
|
-
return 0
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
def _session_id_from_stdin() -> str:
|
|
187
|
-
"""Extract ``session_id`` from a SessionEnd hook's JSON payload on stdin.
|
|
188
|
-
|
|
189
|
-
Returns "" when stdin is a terminal (manual invocation), empty, or not the
|
|
190
|
-
expected JSON — the caller then no-ops. The hook must never block or fail a
|
|
191
|
-
session teardown.
|
|
192
|
-
"""
|
|
193
|
-
if sys.stdin.isatty():
|
|
194
|
-
return ""
|
|
195
|
-
try:
|
|
196
|
-
raw = sys.stdin.read()
|
|
197
|
-
except OSError:
|
|
198
|
-
return ""
|
|
199
|
-
if not raw.strip():
|
|
200
|
-
return ""
|
|
201
|
-
try:
|
|
202
|
-
data = json.loads(raw)
|
|
203
|
-
except json.JSONDecodeError:
|
|
204
|
-
return ""
|
|
205
|
-
sid = data.get("session_id") or data.get("sessionId") or ""
|
|
206
|
-
return sid if isinstance(sid, str) else ""
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
def _run_resolve_and_reconcile(project_root: str, fallback_team: str, dry_run: bool) -> int:
|
|
210
|
-
"""``--project-root`` mode: resolve the live roster, reconcile, list members."""
|
|
211
|
-
cfg = _live_team_config(Path(project_root))
|
|
212
|
-
if cfg is None and fallback_team:
|
|
213
|
-
fb = _config_path(fallback_team)
|
|
214
|
-
cfg = fb if fb.is_file() else None
|
|
215
|
-
if cfg is None:
|
|
216
|
-
print("team-reconcile: no live roster for this session — nothing to dismiss")
|
|
217
|
-
return 0
|
|
218
|
-
print(f"team-reconcile: roster resolved at {cfg}")
|
|
219
|
-
return _reconcile_config(cfg, dry_run, print_members=True)
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
def _run_session_end(dry_run: bool) -> int:
|
|
223
|
-
"""``--session-end`` mode: reconcile the ending session's roster from the
|
|
224
|
-
SessionEnd hook payload. Safety net so a lead that skipped run-end teardown
|
|
225
|
-
still leaves no dead-pane member lingering as a live pill."""
|
|
226
|
-
sid = _session_id_from_stdin()
|
|
227
|
-
if not sid:
|
|
228
|
-
return 0
|
|
229
|
-
cfg = _team_config_for_session(sid)
|
|
230
|
-
if not cfg.is_file():
|
|
231
|
-
return 0
|
|
232
|
-
return _reconcile_config(cfg, dry_run, print_members=False)
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
def _parse_argv(argv: list[str]) -> tuple[bool, str, str, bool, list[str]]:
|
|
236
|
-
dry_run = False
|
|
237
|
-
project_root = ""
|
|
238
|
-
fallback_team = ""
|
|
239
|
-
session_end = False
|
|
240
|
-
rest: list[str] = []
|
|
241
|
-
it = iter(argv)
|
|
242
|
-
for arg in it:
|
|
243
|
-
if arg in ("--list", "--dry-run"):
|
|
244
|
-
dry_run = True
|
|
245
|
-
elif arg == "--project-root":
|
|
246
|
-
project_root = next(it, "")
|
|
247
|
-
elif arg == "--fallback-team":
|
|
248
|
-
fallback_team = next(it, "")
|
|
249
|
-
elif arg == "--session-end":
|
|
250
|
-
session_end = True
|
|
251
|
-
else:
|
|
252
|
-
rest.append(arg)
|
|
253
|
-
return dry_run, project_root, fallback_team, session_end, rest
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
def main(argv: list[str]) -> int:
|
|
257
|
-
dry_run, project_root, fallback_team, session_end, rest = _parse_argv(argv)
|
|
258
|
-
|
|
259
|
-
if session_end:
|
|
260
|
-
return _run_session_end(dry_run)
|
|
261
|
-
if project_root:
|
|
262
|
-
return _run_resolve_and_reconcile(project_root, fallback_team, dry_run)
|
|
263
|
-
|
|
264
|
-
if len(rest) != 1:
|
|
265
|
-
print("usage: okstra-team-reconcile.sh [--list] "
|
|
266
|
-
"(<team-name|team-dir|config.json> | --project-root <dir> "
|
|
267
|
-
"[--fallback-team <label>] | --session-end)",
|
|
268
|
-
file=sys.stderr)
|
|
269
|
-
return 2
|
|
270
|
-
|
|
271
|
-
cfg_path = _config_path(rest[0])
|
|
272
|
-
if not cfg_path.is_file():
|
|
273
|
-
print(f"team-reconcile: no team config at {cfg_path}", file=sys.stderr)
|
|
274
|
-
return 1
|
|
275
|
-
return _reconcile_config(cfg_path, dry_run, print_members=False)
|
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
"""tmux command helpers for okstra-owned container and rerun sessions.
|
|
2
|
-
|
|
3
|
-
Worker dispatch does not come through here. Workers land in a cmux surface or,
|
|
4
|
-
outside cmux, in a cli-wrapper subprocess — neither owns a tmux pane. What is
|
|
5
|
-
left is the two places that use tmux purely as a way to hold a long-lived
|
|
6
|
-
background process: the container watcher/tail panes and `cmd-rerun.sh`'s
|
|
7
|
-
detached spawn.
|
|
8
|
-
"""
|
|
9
|
-
from __future__ import annotations
|
|
10
|
-
|
|
11
|
-
import shlex
|
|
12
|
-
import shutil
|
|
13
|
-
import subprocess
|
|
14
|
-
from typing import Optional, Sequence
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
# container watcher/tail pane 전용 태그. 이 태그가 붙은 pane 은 세션 종료 후에도
|
|
18
|
-
# 생존한다 — watcher/tail 의 "세션 후 생존" 불변식이다. 예전에는 SessionEnd 의
|
|
19
|
-
# 태그 스캔이 다른 태그만 본다는 사실이 그 생존을 지탱했지만, 지금은 그 스캔과
|
|
20
|
-
# 훅과 스크립트 자체가 없어 pane 을 세션 경계에서 회수하는 주체가 아예 없다. 회수는 `down` / `stop-watcher` 의 스코프 reap 뿐이다.
|
|
21
|
-
CONTAINER_TAG_OPTION = "@okstra_container_run"
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
def _shell_quote(s: str) -> str:
|
|
25
|
-
"""POSIX shell 안전 인용. shlex.quote 가 모든 메타문자를 처리한다."""
|
|
26
|
-
return shlex.quote(s)
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
def build_tmux_command(*, session_name: str, cwd: str, run_seq: int,
|
|
30
|
-
argv: list, okstra_script: str,
|
|
31
|
-
extra_env: Optional[dict] = None) -> list:
|
|
32
|
-
"""tmux new-session 명령을 list 형태로 반환."""
|
|
33
|
-
env_prefix = f"OKSTRA_RUN_SEQ_OVERRIDE={run_seq}"
|
|
34
|
-
if extra_env:
|
|
35
|
-
for k, v in extra_env.items():
|
|
36
|
-
env_prefix += f" {k}={_shell_quote(str(v))}"
|
|
37
|
-
inner = (f"{env_prefix} {_shell_quote(okstra_script)} "
|
|
38
|
-
+ " ".join(_shell_quote(a) for a in argv))
|
|
39
|
-
return ["tmux", "new-session", "-d", "-s", session_name, "-c", cwd, inner]
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
def tmux_available() -> bool:
|
|
43
|
-
if shutil.which("tmux") is None:
|
|
44
|
-
return False
|
|
45
|
-
try:
|
|
46
|
-
result = run_tmux(["display-message", "-p", "#{version}"], timeout=3)
|
|
47
|
-
except (OSError, subprocess.SubprocessError):
|
|
48
|
-
return False
|
|
49
|
-
return result.returncode == 0
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
def run_tmux(
|
|
53
|
-
args: Sequence[str], *, timeout: int = 10
|
|
54
|
-
) -> subprocess.CompletedProcess[str]:
|
|
55
|
-
return subprocess.run(
|
|
56
|
-
["tmux", *args],
|
|
57
|
-
capture_output=True,
|
|
58
|
-
text=True,
|
|
59
|
-
timeout=timeout,
|
|
60
|
-
check=False,
|
|
61
|
-
)
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
def new_detached_session(
|
|
65
|
-
session_name: str, cwd: str, first_cmd: str | None = None
|
|
66
|
-
) -> str:
|
|
67
|
-
"""detached tmux 세션을 띄우고 첫 pane id 를 반환한다.
|
|
68
|
-
|
|
69
|
-
cmd-rerun.sh 의 detached spawn 패턴을 python 으로 옮긴 것
|
|
70
|
-
(`tmux new-session -d -s <name> -c <cwd> [cmd]`). `-P -F #{pane_id}` 로
|
|
71
|
-
생성된 pane id 를 받아 이후 split_container_pane 의 `-t` 대상으로 쓴다.
|
|
72
|
-
first_cmd 미지정 시 기본 셸이 첫 pane 을 점유한다 — `true` 처럼 즉시 끝나는
|
|
73
|
-
명령을 주면 단일-윈도우 세션이 split 전에 무너질 수 있으므로(remain-on-exit
|
|
74
|
-
off), holder pane 은 오래 사는 명령이어야 한다.
|
|
75
|
-
"""
|
|
76
|
-
args = ["new-session", "-d", "-s", session_name, "-c", cwd,
|
|
77
|
-
"-P", "-F", "#{pane_id}"]
|
|
78
|
-
if first_cmd:
|
|
79
|
-
args.append(first_cmd)
|
|
80
|
-
result = run_tmux(args)
|
|
81
|
-
if result.returncode != 0:
|
|
82
|
-
raise RuntimeError(result.stderr.strip() or "tmux new-session failed")
|
|
83
|
-
return result.stdout.strip()
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
def split_container_pane(
|
|
87
|
-
*,
|
|
88
|
-
session_pane: Optional[str],
|
|
89
|
-
cwd: str,
|
|
90
|
-
command: str,
|
|
91
|
-
title: str,
|
|
92
|
-
scope_value: str,
|
|
93
|
-
kind: str,
|
|
94
|
-
) -> Optional[str]:
|
|
95
|
-
"""container watcher/tail pane 을 split 하고 container 전용 태그만 부착한다.
|
|
96
|
-
|
|
97
|
-
reap 가 스캔하는 trace 태그는 절대 부착하지 않는다 — CONTAINER_TAG_OPTION 으로
|
|
98
|
-
직접 set-option 한다. session_pane 가 빈값/None(tmux 미사용 경로)이면 raise
|
|
99
|
-
없이 None 반환(degrade).
|
|
100
|
-
"""
|
|
101
|
-
if not session_pane:
|
|
102
|
-
return None
|
|
103
|
-
result = run_tmux(
|
|
104
|
-
["split-window", "-h", "-P", "-F", "#{pane_id}",
|
|
105
|
-
"-c", cwd, "-t", session_pane, command]
|
|
106
|
-
)
|
|
107
|
-
if result.returncode != 0:
|
|
108
|
-
raise RuntimeError(result.stderr.strip() or "tmux split-window failed")
|
|
109
|
-
pane_id = result.stdout.strip()
|
|
110
|
-
set_pane_title(pane_id, title)
|
|
111
|
-
tag_container_pane(pane_id, scope_value)
|
|
112
|
-
return pane_id
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
def tag_container_pane(pane_id: str, scope_value: str) -> None:
|
|
116
|
-
"""container 전용 태그(CONTAINER_TAG_OPTION)만 부착한다.
|
|
117
|
-
|
|
118
|
-
reap 가 스캔하는 trace 태그는 절대 거치지 않는다 — 이 태그가 붙은 pane 은
|
|
119
|
-
세션 종료 후에도 생존하고 `down`/`stop-watcher` 의 스코프 reap 로만
|
|
120
|
-
회수된다. holder pane 도 이 태그로 묶어 회수 대상에 포함한다."""
|
|
121
|
-
run_tmux(["set-option", "-p", "-t", pane_id, CONTAINER_TAG_OPTION, scope_value])
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
def set_pane_title(pane_id: str, title: str) -> None:
|
|
125
|
-
result = run_tmux(["select-pane", "-t", pane_id, "-T", title])
|
|
126
|
-
if result.returncode != 0:
|
|
127
|
-
raise RuntimeError(result.stderr.strip() or "tmux select-pane failed")
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
def kill_pane(pane_id: str) -> None:
|
|
131
|
-
try:
|
|
132
|
-
run_tmux(["kill-pane", "-t", pane_id])
|
|
133
|
-
except (OSError, subprocess.SubprocessError):
|
|
134
|
-
return
|