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
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
"""worktree 를 실제로 만든다 — task 하나에 하나, stage 하나에 하나.
|
|
2
|
+
|
|
3
|
+
이 파일만 부수 효과를 낸다: 레지스트리 예약, worktree 추가, sync 링크 설치,
|
|
4
|
+
실패 시 되감기. 나머지 층은 전부 이 두 함수가 쓰려고 나뉘어 있다.
|
|
5
|
+
`provision_task_worktree` 는 같은 task-key 로 다시 들어오면 기존 worktree 를
|
|
6
|
+
그대로 돌려주고, `provision_stage_worktree` 는 base_commit 을 반드시 요구한다.
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import sys
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
from .. import worktree_registry
|
|
15
|
+
from .decisions import preview_worktree_decision, resolve_stage_worktree_decision
|
|
16
|
+
from .git_ops import (
|
|
17
|
+
_branch_exists,
|
|
18
|
+
_branch_exists_message,
|
|
19
|
+
_git,
|
|
20
|
+
_head_sha,
|
|
21
|
+
_resolve_commit_sha,
|
|
22
|
+
main_worktree_path,
|
|
23
|
+
remove_worktree_force,
|
|
24
|
+
)
|
|
25
|
+
from .linking import (
|
|
26
|
+
_copy_snapshot_files,
|
|
27
|
+
_link_sync_dirs,
|
|
28
|
+
_link_sync_files,
|
|
29
|
+
describe_sync_key_drift,
|
|
30
|
+
sync_file_key_names,
|
|
31
|
+
_seed_worktree_settings_symlink,
|
|
32
|
+
)
|
|
33
|
+
from .naming import _safe_segment
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass
|
|
37
|
+
class WorktreeProvision:
|
|
38
|
+
"""Result of `provision_task_worktree`.
|
|
39
|
+
|
|
40
|
+
status:
|
|
41
|
+
- "created": fresh worktree at `path` on `branch`
|
|
42
|
+
- "reused": registry already had this task-key; same path/branch
|
|
43
|
+
returned and no new `git worktree add` was executed
|
|
44
|
+
- "skipped-in-worktree": project_root is itself a non-main
|
|
45
|
+
worktree; the run reuses `project_root` and no new worktree is
|
|
46
|
+
materialised (registry NOT updated — that caller is already
|
|
47
|
+
isolated by virtue of its own worktree)
|
|
48
|
+
- "skipped-not-git": project_root has no `.git` (worktree path
|
|
49
|
+
cannot be provisioned; degrade gracefully)
|
|
50
|
+
"""
|
|
51
|
+
status: str
|
|
52
|
+
path: str = "" # absolute path of the task worktree (or project_root when reused)
|
|
53
|
+
branch: str = "" # branch checked out in the worktree (empty when reused / not-git)
|
|
54
|
+
base_ref: str = "" # commit SHA the worktree was branched from (empty when not created)
|
|
55
|
+
note: str = "" # human-readable explanation, surfaced in team-state / manifests
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _sync_key_drift_note(main_root: Path, entry) -> str:
|
|
59
|
+
"""재사용되는 worktree 에 붙일 공유 sync 파일 키 드리프트 한 줄.
|
|
60
|
+
|
|
61
|
+
앞에 `; ` 를 붙인 형태로 돌려주므로 note 문자열에 그대로 이어 붙일 수 있고,
|
|
62
|
+
드리프트가 없으면 빈 문자열이라 종전 note 와 바이트가 같다.
|
|
63
|
+
"""
|
|
64
|
+
if entry is None:
|
|
65
|
+
return ""
|
|
66
|
+
message = describe_sync_key_drift(
|
|
67
|
+
entry.sync_file_keys, sync_file_key_names(main_root)
|
|
68
|
+
)
|
|
69
|
+
if not message:
|
|
70
|
+
return ""
|
|
71
|
+
print(f"okstra-worktree: {message}", file=sys.stderr)
|
|
72
|
+
return f"; {message}"
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def provision_task_worktree(
|
|
76
|
+
*,
|
|
77
|
+
task_type: str,
|
|
78
|
+
project_root: Path,
|
|
79
|
+
project_id: str,
|
|
80
|
+
task_group_segment: str,
|
|
81
|
+
task_id_segment: str,
|
|
82
|
+
work_category: str,
|
|
83
|
+
base_ref: str = "",
|
|
84
|
+
require_base_ref: bool = False,
|
|
85
|
+
) -> WorktreeProvision:
|
|
86
|
+
"""Materialise (or reuse) the task worktree for this run.
|
|
87
|
+
|
|
88
|
+
First phase of a task-key creates the worktree on a new branch.
|
|
89
|
+
Subsequent phases of the same task-key look up the registry and
|
|
90
|
+
return the existing path + branch unchanged.
|
|
91
|
+
|
|
92
|
+
``base_ref`` is the ref (branch name, tag, or commit SHA) to branch
|
|
93
|
+
the new worktree from on first phase. When empty, the main worktree's
|
|
94
|
+
current ``HEAD`` is used (legacy default; the CLI enforces a
|
|
95
|
+
non-empty value on first phase so callers go through the
|
|
96
|
+
AskUserQuestion menu in the okstra-run skill). Subsequent phases
|
|
97
|
+
ignore ``base_ref`` — the registered entry's base is reused.
|
|
98
|
+
|
|
99
|
+
Concurrency: callers must hold ``locks.worktree_provision_mutex`` for
|
|
100
|
+
this task-key (run.py's prepare flow does). The exists/branch pre-checks
|
|
101
|
+
and ``git worktree add`` here are not internally locked — only the
|
|
102
|
+
registry reserve row is — so unlocked concurrent calls race (TOCTOU).
|
|
103
|
+
flock is non-reentrant, hence the lock lives at the caller.
|
|
104
|
+
|
|
105
|
+
Raises:
|
|
106
|
+
RuntimeError when worktree creation fails (path clash on disk
|
|
107
|
+
that the registry does not know about, branch clash with a
|
|
108
|
+
different task-key, `git worktree add` non-zero). The caller
|
|
109
|
+
(`run.py`) catches and re-raises as PrepareError to keep a
|
|
110
|
+
single error surface.
|
|
111
|
+
"""
|
|
112
|
+
decision = preview_worktree_decision(
|
|
113
|
+
project_root=project_root, project_id=project_id,
|
|
114
|
+
task_group_segment=task_group_segment, task_id_segment=task_id_segment,
|
|
115
|
+
work_category=work_category, base_ref=base_ref,
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
if decision.status == "skipped-not-git":
|
|
119
|
+
return WorktreeProvision(
|
|
120
|
+
status="skipped-not-git",
|
|
121
|
+
path=decision.path,
|
|
122
|
+
note=(
|
|
123
|
+
"worktree provisioning skipped: project_root is not inside a git "
|
|
124
|
+
"repository; task will operate directly on project_root"
|
|
125
|
+
),
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
if decision.status == "skipped-in-worktree":
|
|
129
|
+
return WorktreeProvision(
|
|
130
|
+
status="skipped-in-worktree",
|
|
131
|
+
path=decision.path,
|
|
132
|
+
note=(
|
|
133
|
+
"worktree provisioning skipped: project_root is already inside a "
|
|
134
|
+
"non-main git worktree; task reuses the caller's worktree"
|
|
135
|
+
),
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
safe_project = _safe_segment(project_id)
|
|
139
|
+
safe_group = _safe_segment(task_group_segment)
|
|
140
|
+
safe_task = _safe_segment(task_id_segment)
|
|
141
|
+
|
|
142
|
+
if decision.status == "reused":
|
|
143
|
+
worktree_registry.touch_phase(safe_project, safe_group, safe_task, task_type)
|
|
144
|
+
_seed_worktree_settings_symlink(Path(decision.path))
|
|
145
|
+
drift = _sync_key_drift_note(
|
|
146
|
+
main_worktree_path(project_root),
|
|
147
|
+
worktree_registry.lookup(safe_project, safe_group, safe_task),
|
|
148
|
+
)
|
|
149
|
+
return WorktreeProvision(
|
|
150
|
+
status="reused",
|
|
151
|
+
path=decision.path,
|
|
152
|
+
branch=decision.branch,
|
|
153
|
+
base_ref=decision.base_ref,
|
|
154
|
+
note=(
|
|
155
|
+
f"task worktree reused at {decision.path} on branch "
|
|
156
|
+
f"{decision.branch} (base {decision.base_ref[:12]}); phase {task_type}"
|
|
157
|
+
f"{drift}"
|
|
158
|
+
),
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
# decision.status == "new" — proceed with creation
|
|
162
|
+
worktree_path = Path(decision.path)
|
|
163
|
+
branch = decision.branch
|
|
164
|
+
|
|
165
|
+
if worktree_path.exists():
|
|
166
|
+
raise RuntimeError(
|
|
167
|
+
f"task worktree path already exists but is not in the registry: "
|
|
168
|
+
f"{worktree_path}. Remove it with `git worktree remove <path>` "
|
|
169
|
+
"(or `rm -rf` if it is not a registered worktree) before retrying."
|
|
170
|
+
)
|
|
171
|
+
if _branch_exists(project_root, branch):
|
|
172
|
+
raise RuntimeError(_branch_exists_message(project_root, branch, "task"))
|
|
173
|
+
|
|
174
|
+
main_root = main_worktree_path(project_root)
|
|
175
|
+
requested_base = (base_ref or "").strip()
|
|
176
|
+
if not requested_base and require_base_ref:
|
|
177
|
+
raise RuntimeError(
|
|
178
|
+
"first-phase task worktree requires an explicit base ref; "
|
|
179
|
+
"pass `--base-ref <branch|tag|sha>` (or invoke through the "
|
|
180
|
+
"okstra-run skill which collects this interactively)"
|
|
181
|
+
)
|
|
182
|
+
if requested_base:
|
|
183
|
+
resolved_sha = _resolve_commit_sha(main_root, requested_base)
|
|
184
|
+
if not resolved_sha:
|
|
185
|
+
raise RuntimeError(
|
|
186
|
+
f"could not resolve base ref `{requested_base}` in main worktree "
|
|
187
|
+
f"({main_root}); ensure the branch/tag/SHA exists locally"
|
|
188
|
+
)
|
|
189
|
+
resolved_base_ref = resolved_sha
|
|
190
|
+
base_origin = requested_base
|
|
191
|
+
else:
|
|
192
|
+
resolved_base_ref = _head_sha(main_root)
|
|
193
|
+
if not resolved_base_ref:
|
|
194
|
+
raise RuntimeError(
|
|
195
|
+
"could not resolve HEAD sha in main worktree; cannot create task worktree"
|
|
196
|
+
)
|
|
197
|
+
base_origin = "HEAD"
|
|
198
|
+
|
|
199
|
+
worktree_path.parent.mkdir(parents=True, exist_ok=True)
|
|
200
|
+
res = _git(
|
|
201
|
+
main_root,
|
|
202
|
+
"worktree", "add", "-b", branch, str(worktree_path), resolved_base_ref,
|
|
203
|
+
)
|
|
204
|
+
if res.returncode != 0:
|
|
205
|
+
raise RuntimeError(
|
|
206
|
+
f"`git worktree add` failed (exit={res.returncode}): "
|
|
207
|
+
f"{(res.stderr or res.stdout).strip()}"
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
# Sync dirs sourced from the MAIN worktree so every task sees the
|
|
211
|
+
# same shared state regardless of which checkout invoked okstra.
|
|
212
|
+
linked = _link_sync_dirs(main_root, worktree_path)
|
|
213
|
+
linked_files = _link_sync_files(main_root, worktree_path)
|
|
214
|
+
sync_file_keys = sync_file_key_names(main_root)
|
|
215
|
+
snapshot_files = _copy_snapshot_files(main_root, worktree_path)
|
|
216
|
+
linked_parts: list[str] = []
|
|
217
|
+
if linked:
|
|
218
|
+
linked_parts.append(f"linked {', '.join(linked)}")
|
|
219
|
+
if linked_files:
|
|
220
|
+
linked_parts.append(f"linked-files {', '.join(linked_files)}")
|
|
221
|
+
if snapshot_files:
|
|
222
|
+
linked_parts.append(f"snapshot {', '.join(snapshot_files)}")
|
|
223
|
+
linked_suffix = ("; " + "; ".join(linked_parts)) if linked_parts else ""
|
|
224
|
+
|
|
225
|
+
try:
|
|
226
|
+
worktree_registry.reserve(
|
|
227
|
+
project_id=safe_project,
|
|
228
|
+
task_group=safe_group,
|
|
229
|
+
task_id=safe_task,
|
|
230
|
+
worktree_path=str(worktree_path),
|
|
231
|
+
branch=branch,
|
|
232
|
+
base_ref=resolved_base_ref,
|
|
233
|
+
phase=task_type,
|
|
234
|
+
sync_file_keys=sync_file_keys,
|
|
235
|
+
)
|
|
236
|
+
except RuntimeError:
|
|
237
|
+
# Roll back the on-disk worktree so the next attempt is not
|
|
238
|
+
# blocked by the lingering directory / branch.
|
|
239
|
+
remove_worktree_force(main_root, worktree_path)
|
|
240
|
+
_git(main_root, "branch", "-D", branch)
|
|
241
|
+
raise
|
|
242
|
+
|
|
243
|
+
_seed_worktree_settings_symlink(worktree_path)
|
|
244
|
+
|
|
245
|
+
base_label = (
|
|
246
|
+
f"{base_origin} @ {resolved_base_ref[:12]}"
|
|
247
|
+
if base_origin != "HEAD"
|
|
248
|
+
else f"HEAD @ {resolved_base_ref[:12]}"
|
|
249
|
+
)
|
|
250
|
+
return WorktreeProvision(
|
|
251
|
+
status="created",
|
|
252
|
+
path=str(worktree_path),
|
|
253
|
+
branch=branch,
|
|
254
|
+
base_ref=resolved_base_ref,
|
|
255
|
+
note=(
|
|
256
|
+
f"task worktree created at {worktree_path} on branch {branch} "
|
|
257
|
+
f"(base {base_label}; phase {task_type}){linked_suffix}"
|
|
258
|
+
),
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def provision_stage_worktree(
|
|
263
|
+
*,
|
|
264
|
+
project_root: Path,
|
|
265
|
+
project_id: str,
|
|
266
|
+
task_group_segment: str,
|
|
267
|
+
task_id_segment: str,
|
|
268
|
+
work_category: str,
|
|
269
|
+
stage_number: int,
|
|
270
|
+
base_commit: str,
|
|
271
|
+
) -> WorktreeProvision:
|
|
272
|
+
"""Materialise an isolated worktree for one implementation stage.
|
|
273
|
+
|
|
274
|
+
Unlike `provision_task_worktree` (one worktree per task-key shared
|
|
275
|
+
across phases), this provisions a per-stage worktree branched from
|
|
276
|
+
`base_commit` at `<task-key>/stage-<N>/` on branch `<prefix>-<task>-s<N>`.
|
|
277
|
+
The stage-key (`<task-key>#stage-<N>`) is reserved atomically through
|
|
278
|
+
`worktree_registry`; re-entry of the same stage-key returns the
|
|
279
|
+
existing entry. Branch / on-disk conflicts roll back the worktree
|
|
280
|
+
before re-raising so a retry is not blocked.
|
|
281
|
+
|
|
282
|
+
Concurrency: callers must hold ``locks.worktree_provision_mutex`` for
|
|
283
|
+
the task-key, acquired BEFORE the Stage Run Claim reads the registry
|
|
284
|
+
(run.py does) — otherwise two `--stage auto` runs can select the same
|
|
285
|
+
stage and the loser silently enters the winner's worktree via the
|
|
286
|
+
"reused" path. flock is non-reentrant, hence the lock lives at the
|
|
287
|
+
caller.
|
|
288
|
+
"""
|
|
289
|
+
if not base_commit:
|
|
290
|
+
raise RuntimeError("provision_stage_worktree requires a base_commit")
|
|
291
|
+
|
|
292
|
+
decision = resolve_stage_worktree_decision(
|
|
293
|
+
project_id=project_id,
|
|
294
|
+
task_group_segment=task_group_segment,
|
|
295
|
+
task_id_segment=task_id_segment,
|
|
296
|
+
work_category=work_category,
|
|
297
|
+
stage_number=stage_number,
|
|
298
|
+
)
|
|
299
|
+
if decision.status == "reused":
|
|
300
|
+
drift = _sync_key_drift_note(
|
|
301
|
+
main_worktree_path(project_root),
|
|
302
|
+
worktree_registry.lookup(
|
|
303
|
+
_safe_segment(project_id),
|
|
304
|
+
_safe_segment(task_group_segment),
|
|
305
|
+
_safe_segment(task_id_segment),
|
|
306
|
+
stage_number=stage_number,
|
|
307
|
+
),
|
|
308
|
+
)
|
|
309
|
+
return WorktreeProvision(
|
|
310
|
+
status="reused",
|
|
311
|
+
path=decision.path,
|
|
312
|
+
branch=decision.branch,
|
|
313
|
+
base_ref=decision.base_ref,
|
|
314
|
+
note=(
|
|
315
|
+
f"stage {stage_number} worktree reused at "
|
|
316
|
+
f"{decision.path} on branch {decision.branch} "
|
|
317
|
+
f"(base {decision.base_ref[:12]}){drift}"
|
|
318
|
+
),
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
safe_project = _safe_segment(project_id)
|
|
322
|
+
safe_group = _safe_segment(task_group_segment)
|
|
323
|
+
safe_task = _safe_segment(task_id_segment)
|
|
324
|
+
worktree_path = Path(decision.path)
|
|
325
|
+
branch = decision.branch
|
|
326
|
+
|
|
327
|
+
if worktree_path.exists():
|
|
328
|
+
raise RuntimeError(
|
|
329
|
+
f"stage worktree path already exists but is not in the registry: "
|
|
330
|
+
f"{worktree_path}. Remove it before retrying."
|
|
331
|
+
)
|
|
332
|
+
if _branch_exists(project_root, branch):
|
|
333
|
+
raise RuntimeError(_branch_exists_message(project_root, branch, "stage"))
|
|
334
|
+
|
|
335
|
+
main_root = main_worktree_path(project_root)
|
|
336
|
+
resolved_sha = _resolve_commit_sha(main_root, base_commit)
|
|
337
|
+
if not resolved_sha:
|
|
338
|
+
raise RuntimeError(
|
|
339
|
+
f"could not resolve base_commit `{base_commit}` in main worktree "
|
|
340
|
+
f"({main_root}); ensure the commit exists locally"
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
worktree_path.parent.mkdir(parents=True, exist_ok=True)
|
|
344
|
+
res = _git(
|
|
345
|
+
main_root,
|
|
346
|
+
"worktree", "add", "-b", branch, str(worktree_path), resolved_sha,
|
|
347
|
+
)
|
|
348
|
+
if res.returncode != 0:
|
|
349
|
+
raise RuntimeError(
|
|
350
|
+
f"`git worktree add` failed (exit={res.returncode}): "
|
|
351
|
+
f"{(res.stderr or res.stdout).strip()}"
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
_link_sync_dirs(main_root, worktree_path)
|
|
355
|
+
_link_sync_files(main_root, worktree_path)
|
|
356
|
+
_copy_snapshot_files(main_root, worktree_path)
|
|
357
|
+
sync_file_keys = sync_file_key_names(main_root)
|
|
358
|
+
|
|
359
|
+
try:
|
|
360
|
+
worktree_registry.reserve(
|
|
361
|
+
project_id=safe_project,
|
|
362
|
+
task_group=safe_group,
|
|
363
|
+
task_id=safe_task,
|
|
364
|
+
worktree_path=str(worktree_path),
|
|
365
|
+
branch=branch,
|
|
366
|
+
base_ref=resolved_sha,
|
|
367
|
+
phase="implementation",
|
|
368
|
+
stage_number=stage_number,
|
|
369
|
+
sync_file_keys=sync_file_keys,
|
|
370
|
+
)
|
|
371
|
+
except RuntimeError:
|
|
372
|
+
remove_worktree_force(main_root, worktree_path)
|
|
373
|
+
_git(main_root, "branch", "-D", branch)
|
|
374
|
+
raise
|
|
375
|
+
|
|
376
|
+
_seed_worktree_settings_symlink(worktree_path)
|
|
377
|
+
|
|
378
|
+
return WorktreeProvision(
|
|
379
|
+
status="created", path=str(worktree_path),
|
|
380
|
+
branch=branch, base_ref=resolved_sha,
|
|
381
|
+
note=(
|
|
382
|
+
f"stage {stage_number} worktree created at {worktree_path} "
|
|
383
|
+
f"on branch {branch} (base {resolved_sha[:12]})"
|
|
384
|
+
),
|
|
385
|
+
)
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
"""worktree 로 옮겨 갈 경로 목록을 결정한다 — 그 목록을 쓰지는 않는다.
|
|
2
|
+
|
|
3
|
+
`git worktree add` 는 추적되지 않는 최상위 항목을 가져오지 않는다. 어떤
|
|
4
|
+
디렉터리·파일이 메인 체크아웃에서 새 worktree 로 따라와야 하는지가 여기서
|
|
5
|
+
정해진다. 우선순위는 세 단계 — 환경 변수, `project.json`, 내장 기본값 — 이고
|
|
6
|
+
앞의 것이 뒤를 REPLACE 한다(병합이 아니다).
|
|
7
|
+
|
|
8
|
+
이 층은 git 을 부르지 않고 심링크도 걸지 않는다. 목록만 답한다.
|
|
9
|
+
"""
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import os
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Optional
|
|
15
|
+
|
|
16
|
+
from okstra_project.dirs import project_json_path
|
|
17
|
+
|
|
18
|
+
from ..json_boundary import JsonBoundaryError, load_owned_object
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# Project-root directories that hold okstra task state, ignored by git, or
|
|
22
|
+
# otherwise required for the executor to operate but NOT carried across by
|
|
23
|
+
# `git worktree add`. Each is symlinked from the MAIN worktree into the new
|
|
24
|
+
# worktree at provision time. Symlinks (not copies) so every task sees the
|
|
25
|
+
# live shared state and disk/CPU cost stays near zero; the trade-off is
|
|
26
|
+
# that any write through the link reaches the main worktree, which is
|
|
27
|
+
# acceptable because okstra writes only to paths it owns: its task-scoped
|
|
28
|
+
# subdirectory (e.g. `.okstra/tasks/<task-id>/runs/...`) and, for a
|
|
29
|
+
# branch-mode code review, `.project-docs/code-reviews/<branch>/`.
|
|
30
|
+
#
|
|
31
|
+
# Override precedence (most-specific first):
|
|
32
|
+
# 1. `OKSTRA_WORKTREE_SYNC_DIRS` env var — colon-separated list, REPLACES
|
|
33
|
+
# defaults. Empty string disables the feature entirely. One-off
|
|
34
|
+
# operator override.
|
|
35
|
+
# 2. `worktreeSyncDirs` array in `.okstra/project.json` —
|
|
36
|
+
# project-level config, persists across runs. Same semantics: array
|
|
37
|
+
# REPLACES defaults, empty array disables.
|
|
38
|
+
# 3. The built-in `DEFAULT_WORKTREE_SYNC_DIRS` below.
|
|
39
|
+
DEFAULT_WORKTREE_SYNC_DIRS: tuple[str, ...] = (
|
|
40
|
+
".project-docs",
|
|
41
|
+
".scratch",
|
|
42
|
+
"graphify-out",
|
|
43
|
+
".claude",
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
# Sync dirs materialised as a REAL directory whose children are symlinked one
|
|
48
|
+
# by one, instead of a single symlink standing in for the whole directory.
|
|
49
|
+
#
|
|
50
|
+
# Why the split exists: git does not follow a symlink, so a symlinked directory
|
|
51
|
+
# is one *file* to it. A project that ignores host config by its contents
|
|
52
|
+
# (`.claude/*`) matches every child but never the bare `.claude` path, so the
|
|
53
|
+
# symlink lands in `git status` as
|
|
54
|
+
# `?? .claude` while the same directory is invisible in the main checkout. Any
|
|
55
|
+
# plan step asserting a clean worktree then fails on okstra's own provisioning.
|
|
56
|
+
# Linking the children instead reproduces the main checkout's shape, so
|
|
57
|
+
# whatever the project's ignore rules do there, they do here too.
|
|
58
|
+
#
|
|
59
|
+
# Only `.claude` qualifies. The other sync dirs are shared okstra state that
|
|
60
|
+
# okstra WRITES into, and a directory symlink is what makes a newly created
|
|
61
|
+
# top-level entry land in the main checkout rather than diverging inside the
|
|
62
|
+
# worktree. `.claude` is host configuration okstra reads; its one okstra write
|
|
63
|
+
# is the fixed `settings.local.json` child seeded by
|
|
64
|
+
# `_seed_worktree_settings_symlink`.
|
|
65
|
+
CHILD_LINKED_SYNC_DIRS: tuple[str, ...] = (".claude",)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
# Project-root-relative FILES (not dirs) symlinked from MAIN → task worktree
|
|
69
|
+
# at provision time. Same symlink semantics as `DEFAULT_WORKTREE_SYNC_DIRS`:
|
|
70
|
+
# every task sees the live shared file. The split exists because the original
|
|
71
|
+
# `_link_sync_dirs` helper only walked directories — a `.env` (or any
|
|
72
|
+
# top-level file outside `.git`'s tracking) would otherwise be lost on every
|
|
73
|
+
# new worktree, blocking verifier dispatches that depend on environment-
|
|
74
|
+
# resolved secrets.
|
|
75
|
+
#
|
|
76
|
+
# Override precedence mirrors `DEFAULT_WORKTREE_SYNC_DIRS`:
|
|
77
|
+
# 1. `OKSTRA_WORKTREE_SYNC_FILES` env var (colon-separated, REPLACES).
|
|
78
|
+
# 2. `worktreeSyncFiles` array in `.okstra/project.json`.
|
|
79
|
+
# 3. The built-in `DEFAULT_WORKTREE_SYNC_FILES` below.
|
|
80
|
+
DEFAULT_WORKTREE_SYNC_FILES: tuple[str, ...] = (
|
|
81
|
+
".env",
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
# Project-root-relative files COPIED (not symlinked) from MAIN → task worktree
|
|
86
|
+
# at provision time, then `chmod 0o444` so the task cannot mutate the
|
|
87
|
+
# snapshot. Used for live-mutating fixtures (e.g. `classifications.db`)
|
|
88
|
+
# where the verifier needs to reproduce accuracy SQL against the same rows
|
|
89
|
+
# the executor saw, without sharing the writable handle that would corrupt
|
|
90
|
+
# the main worktree's copy. Default is empty — okstra has no opinion about
|
|
91
|
+
# which fixtures any given project relies on; opt in per-project via
|
|
92
|
+
# `worktreeSnapshotFiles` in `project.json` (or `OKSTRA_WORKTREE_SNAPSHOT_FILES`
|
|
93
|
+
# for one-off operator override).
|
|
94
|
+
DEFAULT_WORKTREE_SNAPSHOT_FILES: tuple[str, ...] = ()
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _read_project_json_field(project_root: Path, field: str) -> Optional[tuple[str, ...]]:
|
|
98
|
+
"""Read a string-array field from the project's okstra project.json.
|
|
99
|
+
|
|
100
|
+
Returns None if the field is absent or the file cannot be parsed (so
|
|
101
|
+
the caller falls back to defaults). Returns an empty tuple if the
|
|
102
|
+
field is explicitly an empty array (caller treats this as "disable").
|
|
103
|
+
A non-list value is treated as missing — we do not raise here because
|
|
104
|
+
field resolution must never block worktree provisioning.
|
|
105
|
+
"""
|
|
106
|
+
target = project_json_path(project_root)
|
|
107
|
+
if not target.is_file():
|
|
108
|
+
return None
|
|
109
|
+
try:
|
|
110
|
+
data = load_owned_object(target, artifact="project config")
|
|
111
|
+
except (OSError, JsonBoundaryError):
|
|
112
|
+
return None
|
|
113
|
+
if not isinstance(data, dict):
|
|
114
|
+
return None
|
|
115
|
+
value = data.get(field)
|
|
116
|
+
if not isinstance(value, list):
|
|
117
|
+
return None
|
|
118
|
+
cleaned = tuple(
|
|
119
|
+
item.strip() for item in value
|
|
120
|
+
if isinstance(item, str) and item.strip()
|
|
121
|
+
)
|
|
122
|
+
return cleaned
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _resolve_entries(
|
|
126
|
+
*,
|
|
127
|
+
env_var: str,
|
|
128
|
+
project_field: str,
|
|
129
|
+
default: tuple[str, ...],
|
|
130
|
+
project_root: Optional[Path],
|
|
131
|
+
) -> tuple[str, ...]:
|
|
132
|
+
"""Generic resolver shared by sync-dirs / sync-files / snapshot-files.
|
|
133
|
+
|
|
134
|
+
Precedence: env var (colon-separated, REPLACES) → project.json field →
|
|
135
|
+
built-in default. An empty env value or empty JSON array disables the
|
|
136
|
+
feature (returns `()`).
|
|
137
|
+
"""
|
|
138
|
+
raw = os.environ.get(env_var)
|
|
139
|
+
if raw is not None:
|
|
140
|
+
raw = raw.strip()
|
|
141
|
+
if not raw:
|
|
142
|
+
return ()
|
|
143
|
+
return tuple(part for part in (p.strip() for p in raw.split(":")) if part)
|
|
144
|
+
if project_root is not None:
|
|
145
|
+
from_project = _read_project_json_field(project_root, project_field)
|
|
146
|
+
if from_project is not None:
|
|
147
|
+
return from_project
|
|
148
|
+
return default
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def _resolve_sync_dirs(project_root: Optional[Path] = None) -> tuple[str, ...]:
|
|
152
|
+
"""Return the list of project-root-relative dirs to symlink into the
|
|
153
|
+
new worktree. Precedence: env var → project.json → built-in default.
|
|
154
|
+
See the comment above `DEFAULT_WORKTREE_SYNC_DIRS` for full semantics.
|
|
155
|
+
"""
|
|
156
|
+
return _resolve_entries(
|
|
157
|
+
env_var="OKSTRA_WORKTREE_SYNC_DIRS",
|
|
158
|
+
project_field="worktreeSyncDirs",
|
|
159
|
+
default=DEFAULT_WORKTREE_SYNC_DIRS,
|
|
160
|
+
project_root=project_root,
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def _resolve_sync_files(project_root: Optional[Path] = None) -> tuple[str, ...]:
|
|
165
|
+
"""File-level counterpart to `_resolve_sync_dirs` (FU-V2)."""
|
|
166
|
+
return _resolve_entries(
|
|
167
|
+
env_var="OKSTRA_WORKTREE_SYNC_FILES",
|
|
168
|
+
project_field="worktreeSyncFiles",
|
|
169
|
+
default=DEFAULT_WORKTREE_SYNC_FILES,
|
|
170
|
+
project_root=project_root,
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def _resolve_snapshot_files(project_root: Optional[Path] = None) -> tuple[str, ...]:
|
|
175
|
+
"""Read-only snapshot file list (FU-V3)."""
|
|
176
|
+
return _resolve_entries(
|
|
177
|
+
env_var="OKSTRA_WORKTREE_SNAPSHOT_FILES",
|
|
178
|
+
project_field="worktreeSnapshotFiles",
|
|
179
|
+
default=DEFAULT_WORKTREE_SNAPSHOT_FILES,
|
|
180
|
+
project_root=project_root,
|
|
181
|
+
)
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""`okstra worktree-lookup` / `okstra worktree-status` 의 핸들러.
|
|
2
|
+
|
|
3
|
+
원래 `src/commands/execute/worktree-{lookup,status}.mts` 의 문자열 리터럴 안에
|
|
4
|
+
있던 코드다(docs/coding-rules.md R1 위반). 로직은 그대로 옮겼다.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
from dataclasses import asdict
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
from . import worktree_registry
|
|
13
|
+
from .ids import _safe_fs_segment
|
|
14
|
+
from .worktree import (
|
|
15
|
+
dirty_entries_excluding_okstra,
|
|
16
|
+
is_git_work_tree,
|
|
17
|
+
nested_worktree_excludes,
|
|
18
|
+
okstra_clean_gate_excludes,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _entry_payload(entry) -> dict:
|
|
23
|
+
"""registry 의 snake_case dataclass 를 CLI 의 camelCase 계약으로 옮긴다."""
|
|
24
|
+
d = asdict(entry)
|
|
25
|
+
return {
|
|
26
|
+
"taskKey": d.get("task_key"),
|
|
27
|
+
"projectId": d.get("project_id"),
|
|
28
|
+
"taskGroup": d.get("task_group"),
|
|
29
|
+
"taskId": d.get("task_id"),
|
|
30
|
+
"worktreePath": d.get("worktree_path"),
|
|
31
|
+
"branch": d.get("branch"),
|
|
32
|
+
"baseRef": d.get("base_ref"),
|
|
33
|
+
"createdAt": d.get("created_at"),
|
|
34
|
+
"lastPhase": d.get("last_phase"),
|
|
35
|
+
"status": d.get("status"),
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def lookup(args: argparse.Namespace) -> int:
|
|
40
|
+
entry = worktree_registry.lookup(
|
|
41
|
+
_safe_fs_segment(args.project_id),
|
|
42
|
+
_safe_fs_segment(args.task_group),
|
|
43
|
+
_safe_fs_segment(args.task_id),
|
|
44
|
+
)
|
|
45
|
+
if entry is None:
|
|
46
|
+
print(json.dumps({"ok": True, "entry": None}))
|
|
47
|
+
return 0
|
|
48
|
+
print(json.dumps(
|
|
49
|
+
{"ok": True, "entry": _entry_payload(entry)},
|
|
50
|
+
ensure_ascii=False, default=str, indent=2,
|
|
51
|
+
))
|
|
52
|
+
return 0
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def status(args: argparse.Namespace) -> int:
|
|
56
|
+
path = Path(args.path).resolve()
|
|
57
|
+
if not path.is_dir():
|
|
58
|
+
print(json.dumps({"ok": False, "stage": "input",
|
|
59
|
+
"reason": f"not a directory: {path}"}))
|
|
60
|
+
return 2
|
|
61
|
+
if not is_git_work_tree(path):
|
|
62
|
+
print(json.dumps({"ok": False, "stage": "input",
|
|
63
|
+
"reason": f"not inside a git work tree: {path}"}))
|
|
64
|
+
return 2
|
|
65
|
+
|
|
66
|
+
entries = dirty_entries_excluding_okstra(path)
|
|
67
|
+
excluded = [*okstra_clean_gate_excludes(path), *nested_worktree_excludes(path)]
|
|
68
|
+
print(json.dumps({
|
|
69
|
+
"ok": True,
|
|
70
|
+
"path": str(path),
|
|
71
|
+
"clean": not entries,
|
|
72
|
+
"entries": entries,
|
|
73
|
+
"excluded": excluded,
|
|
74
|
+
}, ensure_ascii=False, indent=2))
|
|
75
|
+
return 1 if (args.check_clean and entries) else 0
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""`okstra worktree-lookup` 의 진입점.
|
|
2
|
+
|
|
3
|
+
핸들러는 [worktree_cli.lookup] 에 있다. 이 모듈은 자기 이름의 파서만 갖는다 —
|
|
4
|
+
`python3 -m` 대상 하나가 `okstra` 명령 하나에 대응해야 `--help` 의 prog 와
|
|
5
|
+
사용법이 사용자가 실제로 치는 명령과 같아진다.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import argparse
|
|
10
|
+
|
|
11
|
+
from .worktree_cli import lookup
|
|
12
|
+
|
|
13
|
+
_CLI_EPILOG = r"""Usage:
|
|
14
|
+
okstra worktree-lookup <project-id> <task-group> <task-id>
|
|
15
|
+
|
|
16
|
+
The three identifiers are slugified by the runtime before lookup, so
|
|
17
|
+
case/whitespace variants are tolerated.
|
|
18
|
+
|
|
19
|
+
Output: JSON with the registry entry (worktreePath, branch, baseRef,
|
|
20
|
+
status, lastPhase, createdAt). Emits {"ok": true, "entry": null} when
|
|
21
|
+
the task-key has no active worktree.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def main(argv: list[str] | None = None) -> int:
|
|
26
|
+
parser = argparse.ArgumentParser(
|
|
27
|
+
prog="okstra worktree-lookup",
|
|
28
|
+
description="Look up the registered worktree for a task-key.",
|
|
29
|
+
epilog=_CLI_EPILOG,
|
|
30
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
31
|
+
)
|
|
32
|
+
parser.add_argument("project_id", metavar="project-id")
|
|
33
|
+
parser.add_argument("task_group", metavar="task-group")
|
|
34
|
+
parser.add_argument("task_id", metavar="task-id")
|
|
35
|
+
return lookup(parser.parse_args(argv))
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
if __name__ == "__main__":
|
|
39
|
+
raise SystemExit(main())
|