okstra 0.186.7 → 0.188.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/cli-registry.d.mts +88 -1
- package/dist/cli-registry.mjs +68 -111
- package/dist/cli-registry.mjs.map +1 -1
- package/dist/commands/execute/render-bundle.mjs +0 -1
- package/dist/commands/execute/render-bundle.mjs.map +1 -1
- package/dist/commands/execute/run.mjs +8 -3
- package/dist/commands/execute/run.mjs.map +1 -1
- package/dist/commands/lifecycle/check-project.mjs +1 -14
- package/dist/commands/lifecycle/check-project.mjs.map +1 -1
- package/dist/commands/lifecycle/config.mjs +38 -40
- package/dist/commands/lifecycle/config.mjs.map +1 -1
- package/dist/commands/lifecycle/doctor.d.mts +22 -7
- package/dist/commands/lifecycle/doctor.mjs +77 -49
- package/dist/commands/lifecycle/doctor.mjs.map +1 -1
- package/dist/commands/lifecycle/install.d.mts +12 -10
- package/dist/commands/lifecycle/install.mjs +107 -40
- package/dist/commands/lifecycle/install.mjs.map +1 -1
- package/dist/commands/lifecycle/paths.mjs +8 -3
- package/dist/commands/lifecycle/paths.mjs.map +1 -1
- package/dist/commands/lifecycle/preflight.mjs +2 -1
- package/dist/commands/lifecycle/preflight.mjs.map +1 -1
- package/dist/commands/lifecycle/setup.mjs +22 -36
- package/dist/commands/lifecycle/setup.mjs.map +1 -1
- package/dist/commands/lifecycle/uninstall.d.mts +4 -2
- package/dist/commands/lifecycle/uninstall.mjs +59 -15
- package/dist/commands/lifecycle/uninstall.mjs.map +1 -1
- package/dist/commands/memory/memory.mjs +7 -1
- package/dist/commands/memory/memory.mjs.map +1 -1
- package/dist/lib/helper-scripts.d.mts +1 -1
- package/dist/lib/helper-scripts.mjs +10 -18
- package/dist/lib/helper-scripts.mjs.map +1 -1
- package/dist/lib/host-config.d.mts +72 -0
- package/dist/lib/host-config.mjs +404 -0
- package/dist/lib/host-config.mjs.map +1 -0
- package/dist/lib/host-registry-client.d.mts +2 -2
- package/dist/lib/host-registry-client.mjs +0 -3
- package/dist/lib/host-registry-client.mjs.map +1 -1
- package/dist/lib/install-assets.d.mts +1 -0
- package/dist/lib/install-assets.mjs +4 -0
- package/dist/lib/install-assets.mjs.map +1 -1
- package/dist/lib/proc.d.mts +2 -0
- package/dist/lib/proc.mjs +12 -0
- package/dist/lib/proc.mjs.map +1 -1
- package/dist/lib/python-command.d.mts +2 -0
- package/dist/lib/python-command.mjs +52 -0
- package/dist/lib/python-command.mjs.map +1 -0
- package/dist/lib/python-helper.d.mts +2 -5
- package/dist/lib/python-helper.mjs +3 -52
- package/dist/lib/python-helper.mjs.map +1 -1
- package/dist/lib/runtime-payload.d.mts +23 -0
- package/dist/lib/runtime-payload.mjs +57 -0
- package/dist/lib/runtime-payload.mjs.map +1 -0
- package/dist/lib/types.d.mts +24 -13
- package/docs/architecture/storage-model.md +21 -5
- package/docs/architecture.md +39 -38
- package/docs/cli.md +55 -76
- package/docs/coding-rules.md +295 -0
- package/docs/container.md +10 -36
- package/docs/contributor-change-matrix.md +1 -1
- package/docs/for-ai/skills/okstra-code-review.md +0 -1
- package/docs/for-ai/skills/okstra-container-build.md +8 -41
- package/docs/for-ai/skills/okstra-inspect.md +4 -4
- package/docs/for-ai/skills/okstra-manager.md +2 -2
- package/docs/for-ai/skills/okstra-rollup.md +0 -1
- package/docs/for-ai/skills/okstra-run.md +3 -3
- package/docs/for-ai/skills/okstra-user-response.md +0 -1
- package/docs/performance-improvement-plan-v2.md +1 -1
- package/docs/project-structure-overview.md +62 -62
- package/docs/task-process/README.md +3 -3
- package/docs/task-process/common-flow.md +1 -1
- package/docs/task-process/error-analysis.md +3 -3
- package/docs/task-process/implementation-planning.md +3 -1
- package/docs/task-process/release-handoff.md +1 -1
- package/package.json +3 -2
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/workers/claude-worker.md +6 -11
- package/runtime/agents/workers/report-writer-worker.md +1 -1
- package/runtime/bin/lib/okstra/cli.sh +4 -0
- package/runtime/bin/lib/okstra/globals.sh +2 -0
- package/runtime/bin/lib/okstra/interactive.sh +26 -173
- package/runtime/bin/lib/okstra/project-resolver.sh +23 -61
- package/runtime/bin/lib/okstra/usage.sh +3 -3
- package/runtime/bin/okstra-compact-reminder.sh +1 -5
- package/runtime/bin/okstra-error-log.py +19 -2
- package/runtime/bin/okstra-import-check.py +26 -0
- package/runtime/bin/okstra-inject-report-index.py +5 -4
- package/runtime/bin/okstra-provider-exec.py +22 -17
- package/runtime/bin/okstra-render-final-report.py +16 -6
- package/runtime/bin/okstra-render-report-views.py +47 -22
- package/runtime/bin/okstra-report-translate.py +30 -18
- package/runtime/bin/okstra-spawn-followups.py +4 -0
- package/runtime/bin/okstra-token-usage.py +3 -0
- package/runtime/bin/okstra.sh +4 -2
- package/runtime/bin/okstra_bootstrap.py +58 -0
- package/runtime/prompts/coding-preflight/overview.md +1 -1
- package/runtime/prompts/duties/planning-worker.md +1 -1
- package/runtime/prompts/launch.template.md +35 -9
- package/runtime/prompts/lead/adapters/cmux.md +3 -3
- package/runtime/prompts/lead/convergence.md +99 -88
- package/runtime/prompts/lead/okstra-lead-contract.md +63 -12
- package/runtime/prompts/lead/plan-body-verification.md +65 -46
- package/runtime/prompts/lead/report-writer.md +35 -5
- package/runtime/prompts/lead/team-contract.md +4 -1
- package/runtime/prompts/profiles/_clarification-recommendation.md +2 -1
- package/runtime/prompts/profiles/_coding-conventions-preflight.md +1 -1
- package/runtime/prompts/profiles/_common-contract.md +3 -3
- package/runtime/prompts/profiles/_coverage-critic.md +1 -1
- package/runtime/prompts/profiles/_implementation-deliverable.md +2 -2
- package/runtime/prompts/profiles/_implementation-executor.md +3 -1
- package/runtime/prompts/profiles/_implementation-verifier.md +16 -2
- package/runtime/prompts/profiles/error-analysis.md +4 -3
- package/runtime/prompts/profiles/final-verification.md +2 -2
- package/runtime/prompts/profiles/implementation-planning.md +22 -17
- package/runtime/prompts/profiles/implementation.md +3 -2
- package/runtime/prompts/profiles/requirements-discovery.md +2 -1
- package/runtime/prompts/wizard/prompts.ko.json +29 -8
- package/runtime/python/okstra_ctl/__init__.py +6 -27
- package/runtime/python/okstra_ctl/adapters/hosts/claude-code/relay.md +4 -5
- package/runtime/python/okstra_ctl/adapters/hosts/codex/adapter.py +21 -2
- package/runtime/python/okstra_ctl/adapters/hosts/external/adapter.py +3 -12
- package/runtime/python/okstra_ctl/adapters/providers/antigravity/adapter.py +63 -22
- package/runtime/python/okstra_ctl/adapters/providers/claude/adapter.py +20 -21
- package/runtime/python/okstra_ctl/adapters/providers/codex/adapter.py +37 -20
- package/runtime/python/okstra_ctl/adapters/providers/grok/adapter.py +86 -14
- package/runtime/python/okstra_ctl/adapters/providers/kimi/adapter.py +12 -12
- package/runtime/python/okstra_ctl/adapters/runtime/cmux.py +4 -7
- package/runtime/python/okstra_ctl/agent/__init__.py +1 -0
- package/runtime/python/okstra_ctl/{agent_activity.py → agent/activity.py} +16 -2
- package/runtime/python/okstra_ctl/{agent_invocation.py → agent/invocation.py} +34 -5
- package/runtime/python/okstra_ctl/agent/prompt_cli/__init__.py +19 -0
- package/runtime/python/okstra_ctl/agent/prompt_cli/__main__.py +11 -0
- package/runtime/python/okstra_ctl/agent/prompt_cli/cli.py +228 -0
- package/runtime/python/okstra_ctl/agent/prompt_cli/dynamic_verifier.py +155 -0
- package/runtime/python/okstra_ctl/agent/prompt_cli/emit.py +66 -0
- package/runtime/python/okstra_ctl/agent/prompt_cli/inputs.py +138 -0
- package/runtime/python/okstra_ctl/agent/prompt_cli/materialize.py +605 -0
- package/runtime/python/okstra_ctl/agent/prompt_cli/results.py +197 -0
- package/runtime/python/okstra_ctl/agent/prompt_cli/run_identity.py +107 -0
- package/runtime/python/okstra_ctl/analysis_packet.py +47 -4
- package/runtime/python/okstra_ctl/approval_decisions.py +287 -10
- package/runtime/python/okstra_ctl/assignment_resolver.py +0 -12
- package/runtime/python/okstra_ctl/attempt_evidence.py +12 -24
- package/runtime/python/okstra_ctl/blocking_checks.py +251 -0
- package/runtime/python/okstra_ctl/brief_frontmatter.py +0 -7
- package/runtime/python/okstra_ctl/clarification_items/__init__.py +102 -0
- package/runtime/python/okstra_ctl/clarification_items/carry.py +224 -0
- package/runtime/python/okstra_ctl/clarification_items/dispositions.py +135 -0
- package/runtime/python/okstra_ctl/clarification_items/parsing.py +253 -0
- package/runtime/python/okstra_ctl/clarification_items/rows.py +115 -0
- package/runtime/python/okstra_ctl/clarification_items/scan.py +215 -0
- package/runtime/python/okstra_ctl/clarification_items/sidecars.py +211 -0
- package/runtime/python/okstra_ctl/cmux.py +38 -31
- package/runtime/python/okstra_ctl/code_review_target.py +163 -1
- package/runtime/python/okstra_ctl/conformance.py +23 -0
- package/runtime/python/okstra_ctl/container.py +38 -421
- package/runtime/python/okstra_ctl/context_cost.py +32 -4
- package/runtime/python/okstra_ctl/contract_graph_cli.py +7 -0
- package/runtime/python/okstra_ctl/convergence.py +107 -16
- package/runtime/python/okstra_ctl/convergence_critic_prompt.py +345 -0
- package/runtime/python/okstra_ctl/convergence_engine.py +382 -70
- package/runtime/python/okstra_ctl/convergence_store.py +5 -29
- package/runtime/python/okstra_ctl/design_prep.py +108 -12
- package/runtime/python/okstra_ctl/design_snapshot.py +11 -1
- package/runtime/python/okstra_ctl/design_surfaces.py +29 -2
- package/runtime/python/okstra_ctl/dispatch_core.py +236 -49
- package/runtime/python/okstra_ctl/dispatch_state.py +417 -41
- package/runtime/python/okstra_ctl/doctor_cli.py +48 -0
- package/runtime/python/okstra_ctl/domain/worker_exec.py +10 -5
- package/runtime/python/okstra_ctl/domain/worker_presentation.py +28 -1
- package/runtime/python/okstra_ctl/domain/worker_stream.py +52 -21
- package/runtime/python/okstra_ctl/domain/write_policy.py +219 -0
- package/runtime/python/okstra_ctl/entrypoints/hosts.py +9 -2
- package/runtime/python/okstra_ctl/error_log_core.py +1 -1
- package/runtime/python/okstra_ctl/error_report.py +14 -0
- package/runtime/python/okstra_ctl/error_zip.py +18 -2
- package/runtime/python/okstra_ctl/exact_coverage.py +5 -0
- package/runtime/python/okstra_ctl/execution_identity.py +100 -7
- package/runtime/python/okstra_ctl/execution_manifest.py +67 -20
- package/runtime/python/okstra_ctl/execution_mutation_audit.py +90 -8
- package/runtime/python/okstra_ctl/final_report_paths.py +17 -0
- package/runtime/python/okstra_ctl/final_report_schema.py +319 -17
- package/runtime/python/okstra_ctl/git_reconcile.py +22 -2
- package/runtime/python/okstra_ctl/handoff.py +24 -1
- package/runtime/python/okstra_ctl/ids.py +6 -16
- package/runtime/python/okstra_ctl/implementation_direction.py +142 -33
- package/runtime/python/okstra_ctl/implementation_options.py +32 -13
- package/runtime/python/okstra_ctl/implementation_outcome.py +5 -2
- package/runtime/python/okstra_ctl/improvement_lenses.py +0 -14
- package/runtime/python/okstra_ctl/incremental_carry.py +69 -8
- package/runtime/python/okstra_ctl/incremental_scope.py +173 -12
- package/runtime/python/okstra_ctl/index.py +2 -2
- package/runtime/python/okstra_ctl/initial_prompt_materialization.py +48 -8
- package/runtime/python/okstra_ctl/interactive_cli.py +223 -0
- package/runtime/python/okstra_ctl/json_boundary.py +10 -0
- package/runtime/python/okstra_ctl/listing.py +0 -91
- package/runtime/python/okstra_ctl/locks.py +5 -19
- package/runtime/python/okstra_ctl/log_report.py +14 -0
- package/runtime/python/okstra_ctl/manager_cli.py +22 -3
- package/runtime/python/okstra_ctl/manager_launch.py +4 -8
- package/runtime/python/okstra_ctl/material.py +2 -2
- package/runtime/python/okstra_ctl/migrate.py +23 -1
- package/runtime/python/okstra_ctl/model_cli.py +19 -5
- package/runtime/python/okstra_ctl/model_discovery.py +46 -47
- package/runtime/python/okstra_ctl/model_io/__init__.py +1 -0
- package/runtime/python/okstra_ctl/model_io/lines.py +163 -0
- package/runtime/python/okstra_ctl/model_io/references.py +370 -0
- package/runtime/python/okstra_ctl/model_io/renderers.py +498 -0
- package/runtime/python/okstra_ctl/model_io_cli.py +30 -945
- package/runtime/python/okstra_ctl/model_pool.py +10 -6
- package/runtime/python/okstra_ctl/models.py +8 -8
- package/runtime/python/okstra_ctl/mutation_recovery.py +5 -63
- package/runtime/python/okstra_ctl/next_phase.py +121 -69
- package/runtime/python/okstra_ctl/pane_reclaim.py +23 -0
- package/runtime/python/okstra_ctl/pane_title.py +11 -8
- package/runtime/python/okstra_ctl/path_hints.py +36 -11
- package/runtime/python/okstra_ctl/paths.py +76 -8
- package/runtime/python/okstra_ctl/plan_items.py +132 -16
- package/runtime/python/okstra_ctl/plan_items_cli.py +486 -36
- package/runtime/python/okstra_ctl/plan_run_root.py +1 -1
- package/runtime/python/okstra_ctl/plan_validate_cli.py +51 -0
- package/runtime/python/okstra_ctl/plan_verify_cli.py +80 -0
- package/runtime/python/okstra_ctl/prepare_error.py +20 -0
- package/runtime/python/okstra_ctl/prior_planning.py +157 -0
- package/runtime/python/okstra_ctl/profile_show.py +18 -1
- package/runtime/python/okstra_ctl/project_setup_cli.py +140 -0
- package/runtime/python/okstra_ctl/recap.py +22 -1
- package/runtime/python/okstra_ctl/reconcile.py +84 -0
- package/runtime/python/okstra_ctl/registry/host_registry.py +18 -1
- package/runtime/python/okstra_ctl/render.py +98 -32
- package/runtime/python/okstra_ctl/render_final_report.py +4 -8
- package/runtime/python/okstra_ctl/report_assembly.py +297 -7
- package/runtime/python/okstra_ctl/report_contract.py +30 -7
- package/runtime/python/okstra_ctl/report_finalize.py +213 -16
- package/runtime/python/okstra_ctl/report_html/common.py +2 -2
- package/runtime/python/okstra_ctl/report_html/render.py +8 -12
- package/runtime/python/okstra_ctl/report_language.py +8 -4
- package/runtime/python/okstra_ctl/report_narrative.py +220 -34
- package/runtime/python/okstra_ctl/report_projections.py +8 -2
- package/runtime/python/okstra_ctl/report_synthesis_packet.py +393 -1
- package/runtime/python/okstra_ctl/report_translation.py +2 -35
- package/runtime/python/okstra_ctl/report_view_artifacts.py +42 -2
- package/runtime/python/okstra_ctl/report_views.py +4 -22
- package/runtime/python/okstra_ctl/resolve_task_key.py +13 -0
- package/runtime/python/okstra_ctl/rollup.py +13 -0
- package/runtime/python/okstra_ctl/run.py +639 -138
- package/runtime/python/okstra_ctl/run_audit.py +13 -0
- package/runtime/python/okstra_ctl/run_index_row.py +0 -12
- package/runtime/python/okstra_ctl/seeding.py +21 -11
- package/runtime/python/okstra_ctl/sequence.py +1 -1
- package/runtime/python/okstra_ctl/session.py +64 -14
- package/runtime/python/okstra_ctl/set_work_status.py +18 -0
- package/runtime/python/okstra_ctl/stage_integrate.py +15 -1
- package/runtime/python/okstra_ctl/stage_ledger.py +1 -1
- package/runtime/python/okstra_ctl/stage_map.py +57 -11
- package/runtime/python/okstra_ctl/stage_map_cli.py +83 -0
- package/runtime/python/okstra_ctl/stage_map_view.py +65 -0
- package/runtime/python/okstra_ctl/stage_targets.py +3 -12
- package/runtime/python/okstra_ctl/task_list_cli.py +138 -0
- package/runtime/python/okstra_ctl/task_show_cli.py +66 -0
- package/runtime/python/okstra_ctl/task_target.py +4 -16
- package/runtime/python/okstra_ctl/team.py +49 -2
- package/runtime/python/okstra_ctl/time_report.py +17 -2
- package/runtime/python/okstra_ctl/usage_report.py +11 -0
- package/runtime/python/okstra_ctl/user_response.py +55 -218
- package/runtime/python/okstra_ctl/user_response_values.py +242 -0
- package/runtime/python/okstra_ctl/validation_contract.py +3 -0
- package/runtime/python/okstra_ctl/verdict_blocks.py +28 -8
- package/runtime/python/okstra_ctl/verification_target.py +68 -0
- package/runtime/python/okstra_ctl/wizard.py +457 -92
- package/runtime/python/okstra_ctl/worker_artifacts.py +75 -16
- package/runtime/python/okstra_ctl/worker_audit_check.py +22 -0
- package/runtime/python/okstra_ctl/worker_dispatch.py +21 -0
- package/runtime/python/okstra_ctl/worker_liveness.py +33 -0
- package/runtime/python/okstra_ctl/worker_prompt_body.py +15 -2
- package/runtime/python/okstra_ctl/worker_prompt_contract.py +98 -7
- package/runtime/python/okstra_ctl/worker_prompt_headers.py +24 -2
- package/runtime/python/okstra_ctl/worker_prompt_policy.py +18 -3
- package/runtime/python/okstra_ctl/worker_request.py +2 -1
- package/runtime/python/okstra_ctl/worker_runner.py +58 -17
- package/runtime/python/okstra_ctl/worker_state.py +16 -1
- package/runtime/python/okstra_ctl/workflow.py +18 -1
- package/runtime/python/okstra_ctl/worktree/__init__.py +92 -0
- package/runtime/python/okstra_ctl/worktree/cleanliness.py +89 -0
- package/runtime/python/okstra_ctl/worktree/decisions.py +127 -0
- package/runtime/python/okstra_ctl/worktree/git_ops.py +165 -0
- package/runtime/python/okstra_ctl/worktree/linking.py +249 -0
- package/runtime/python/okstra_ctl/worktree/naming.py +91 -0
- package/runtime/python/okstra_ctl/worktree/provision.py +385 -0
- package/runtime/python/okstra_ctl/worktree/sync_config.py +181 -0
- package/runtime/python/okstra_ctl/worktree_cli.py +75 -0
- package/runtime/python/okstra_ctl/worktree_lookup_cli.py +39 -0
- package/runtime/python/okstra_ctl/worktree_registry.py +7 -0
- package/runtime/python/okstra_ctl/worktree_status_cli.py +53 -0
- package/runtime/python/okstra_ctl/write_policy.py +90 -213
- package/runtime/python/okstra_project/__init__.py +0 -4
- package/runtime/python/okstra_project/dirs.py +2 -2
- package/runtime/python/okstra_project/phase_pointer.py +84 -0
- package/runtime/python/okstra_project/slug.py +24 -0
- package/runtime/python/okstra_project/state.py +22 -230
- package/runtime/python/okstra_token_usage/antigravity.py +50 -12
- package/runtime/python/okstra_token_usage/claude.py +9 -1
- package/runtime/python/okstra_token_usage/cli.py +3 -1
- package/runtime/python/okstra_token_usage/collect.py +171 -36
- package/runtime/python/okstra_token_usage/cursor.py +2 -0
- package/runtime/python/okstra_token_usage/grok.py +1 -5
- package/runtime/python/okstra_token_usage/paths.py +26 -0
- package/runtime/python/okstra_token_usage/pricing.py +23 -8
- package/runtime/schemas/convergence-critic-results-v1.0.schema.json +5 -0
- package/runtime/schemas/execution-manifest-v2.schema.json +10 -10
- package/runtime/schemas/final-report-v2.0.schema.json +9 -26
- package/runtime/schemas/final-report-v3.0.schema.json +173 -41
- package/runtime/schemas/report-narrative-v3.0.schema.json +3 -2
- package/runtime/skills/okstra-brief-gen/SKILL.md +35 -2
- package/runtime/skills/okstra-container-build/SKILL.md +16 -47
- package/runtime/skills/okstra-inspect/facets/history.md +1 -1
- package/runtime/skills/okstra-inspect/facets/status.md +7 -6
- package/runtime/skills/okstra-pr-gen/SKILL.md +1 -1
- package/runtime/skills/okstra-run/SKILL.md +7 -5
- package/runtime/templates/report-writer-prompt-preamble.md +3 -1
- package/runtime/templates/reports/brief.template.md +6 -2
- package/runtime/templates/reports/error-analysis-input.template.md +2 -0
- package/runtime/templates/reports/final-verification-input.template.md +2 -0
- package/runtime/templates/reports/implementation-input.template.md +7 -1
- package/runtime/templates/reports/implementation-planning-input.template.md +4 -0
- package/runtime/templates/reports/quick-input.template.md +2 -0
- package/runtime/templates/reports/release-handoff-input.template.md +6 -1
- package/runtime/templates/reports/report.js +20 -3
- package/runtime/templates/reports/schedule.template.md +2 -0
- package/runtime/templates/reports/settings.template.json +0 -10
- package/runtime/templates/reports/task-brief.template.md +3 -0
- package/runtime/templates/reports/user-response.template.md +7 -3
- package/runtime/validators/checks/fixtures-01.py +57 -0
- package/runtime/validators/checks/fixtures-02.py +565 -0
- package/runtime/validators/checks/runners-01.py +108 -0
- package/runtime/validators/checks/validate-assets-01.py +60 -0
- package/runtime/validators/checks/validate-prompt-metadata-01.py +261 -0
- package/runtime/validators/checks/validate-tasks-01.py +46 -0
- package/runtime/validators/checks/validate-tasks-02.py +85 -0
- package/runtime/validators/checks/validate-tasks-03.py +61 -0
- package/runtime/validators/checks/validate-tasks-04.py +118 -0
- package/runtime/validators/forbidden_actions.py +73 -3
- package/runtime/validators/lib/common.sh +5 -0
- package/runtime/validators/lib/fixtures.sh +7 -591
- package/runtime/validators/lib/paths.sh +13 -0
- package/runtime/validators/lib/runners.sh +6 -104
- package/runtime/validators/lib/summary.sh +1 -1
- package/runtime/validators/lib/validate-assets.sh +6 -56
- package/runtime/validators/lib/validate-prompt-metadata.sh +6 -257
- package/runtime/validators/lib/validate-tasks.sh +9 -294
- package/runtime/validators/validate-implementation-plan-stages.py +4 -4
- package/runtime/validators/validate-run.py +1363 -2643
- package/runtime/validators/validate-workflow.sh +56 -16
- package/runtime/validators/validate_improvement_report.py +2 -1
- package/runtime/validators/validate_session_conformance.py +295 -49
- package/dist/commands/execute/agent-prompt.d.mts +0 -1
- package/dist/commands/execute/agent-prompt.mjs +0 -24
- package/dist/commands/execute/agent-prompt.mjs.map +0 -1
- package/dist/commands/execute/codex-dispatch.d.mts +0 -3
- package/dist/commands/execute/codex-dispatch.mjs +0 -6
- package/dist/commands/execute/codex-dispatch.mjs.map +0 -1
- package/dist/commands/execute/codex-run.d.mts +0 -3
- package/dist/commands/execute/codex-run.mjs +0 -62
- package/dist/commands/execute/codex-run.mjs.map +0 -1
- package/dist/commands/execute/convergence.d.mts +0 -1
- package/dist/commands/execute/convergence.mjs +0 -38
- package/dist/commands/execute/convergence.mjs.map +0 -1
- package/dist/commands/execute/error-log.d.mts +0 -1
- package/dist/commands/execute/error-log.mjs +0 -18
- package/dist/commands/execute/error-log.mjs.map +0 -1
- package/dist/commands/execute/git-reconcile.d.mts +0 -1
- package/dist/commands/execute/git-reconcile.mjs +0 -30
- package/dist/commands/execute/git-reconcile.mjs.map +0 -1
- package/dist/commands/execute/handoff.d.mts +0 -1
- package/dist/commands/execute/handoff.mjs +0 -31
- package/dist/commands/execute/handoff.mjs.map +0 -1
- package/dist/commands/execute/incremental-carry.d.mts +0 -1
- package/dist/commands/execute/incremental-carry.mjs +0 -20
- package/dist/commands/execute/incremental-carry.mjs.map +0 -1
- package/dist/commands/execute/incremental-scope.d.mts +0 -1
- package/dist/commands/execute/incremental-scope.mjs +0 -29
- package/dist/commands/execute/incremental-scope.mjs.map +0 -1
- package/dist/commands/execute/integrate-stages.d.mts +0 -1
- package/dist/commands/execute/integrate-stages.mjs +0 -24
- package/dist/commands/execute/integrate-stages.mjs.map +0 -1
- package/dist/commands/execute/pane-title.d.mts +0 -1
- package/dist/commands/execute/pane-title.mjs +0 -20
- package/dist/commands/execute/pane-title.mjs.map +0 -1
- package/dist/commands/execute/plan-items.d.mts +0 -1
- package/dist/commands/execute/plan-items.mjs +0 -9
- package/dist/commands/execute/plan-items.mjs.map +0 -1
- package/dist/commands/execute/plan-validate.d.mts +0 -1
- package/dist/commands/execute/plan-validate.mjs +0 -68
- package/dist/commands/execute/plan-validate.mjs.map +0 -1
- package/dist/commands/execute/plan-verify.d.mts +0 -1
- package/dist/commands/execute/plan-verify.mjs +0 -43
- package/dist/commands/execute/plan-verify.mjs.map +0 -1
- package/dist/commands/execute/spawn-followups.d.mts +0 -1
- package/dist/commands/execute/spawn-followups.mjs +0 -22
- package/dist/commands/execute/spawn-followups.mjs.map +0 -1
- package/dist/commands/execute/team.d.mts +0 -3
- package/dist/commands/execute/team.mjs +0 -66
- package/dist/commands/execute/team.mjs.map +0 -1
- package/dist/commands/execute/token-usage.d.mts +0 -1
- package/dist/commands/execute/token-usage.mjs +0 -19
- package/dist/commands/execute/token-usage.mjs.map +0 -1
- package/dist/commands/execute/worker-audit-check.d.mts +0 -1
- package/dist/commands/execute/worker-audit-check.mjs +0 -34
- package/dist/commands/execute/worker-audit-check.mjs.map +0 -1
- package/dist/commands/execute/worker-dispatch.d.mts +0 -7
- package/dist/commands/execute/worker-dispatch.mjs +0 -64
- package/dist/commands/execute/worker-dispatch.mjs.map +0 -1
- package/dist/commands/execute/worker-state.d.mts +0 -1
- package/dist/commands/execute/worker-state.mjs +0 -28
- package/dist/commands/execute/worker-state.mjs.map +0 -1
- package/dist/commands/execute/worktree-lookup.d.mts +0 -1
- package/dist/commands/execute/worktree-lookup.mjs +0 -92
- package/dist/commands/execute/worktree-lookup.mjs.map +0 -1
- package/dist/commands/execute/worktree-status.d.mts +0 -1
- package/dist/commands/execute/worktree-status.mjs +0 -121
- package/dist/commands/execute/worktree-status.mjs.map +0 -1
- package/dist/commands/inspect/code-review.d.mts +0 -1
- package/dist/commands/inspect/code-review.mjs +0 -32
- package/dist/commands/inspect/code-review.mjs.map +0 -1
- package/dist/commands/inspect/container.d.mts +0 -1
- package/dist/commands/inspect/container.mjs +0 -25
- package/dist/commands/inspect/container.mjs.map +0 -1
- package/dist/commands/inspect/context-cost.d.mts +0 -1
- package/dist/commands/inspect/context-cost.mjs +0 -25
- package/dist/commands/inspect/context-cost.mjs.map +0 -1
- package/dist/commands/inspect/design-prep.d.mts +0 -1
- package/dist/commands/inspect/design-prep.mjs +0 -22
- package/dist/commands/inspect/design-prep.mjs.map +0 -1
- package/dist/commands/inspect/error-report.d.mts +0 -1
- package/dist/commands/inspect/error-report.mjs +0 -25
- package/dist/commands/inspect/error-report.mjs.map +0 -1
- package/dist/commands/inspect/error-zip.d.mts +0 -1
- package/dist/commands/inspect/error-zip.mjs +0 -24
- package/dist/commands/inspect/error-zip.mjs.map +0 -1
- package/dist/commands/inspect/log-report.d.mts +0 -1
- package/dist/commands/inspect/log-report.mjs +0 -26
- package/dist/commands/inspect/log-report.mjs.map +0 -1
- package/dist/commands/inspect/model-io.d.mts +0 -1
- package/dist/commands/inspect/model-io.mjs +0 -25
- package/dist/commands/inspect/model-io.mjs.map +0 -1
- package/dist/commands/inspect/profile-show.d.mts +0 -1
- package/dist/commands/inspect/profile-show.mjs +0 -28
- package/dist/commands/inspect/profile-show.mjs.map +0 -1
- package/dist/commands/inspect/recap.d.mts +0 -1
- package/dist/commands/inspect/recap.mjs +0 -30
- package/dist/commands/inspect/recap.mjs.map +0 -1
- package/dist/commands/inspect/resolve-task-key.d.mts +0 -1
- package/dist/commands/inspect/resolve-task-key.mjs +0 -24
- package/dist/commands/inspect/resolve-task-key.mjs.map +0 -1
- package/dist/commands/inspect/rollup.d.mts +0 -1
- package/dist/commands/inspect/rollup.mjs +0 -25
- package/dist/commands/inspect/rollup.mjs.map +0 -1
- package/dist/commands/inspect/run-audit.d.mts +0 -1
- package/dist/commands/inspect/run-audit.mjs +0 -25
- package/dist/commands/inspect/run-audit.mjs.map +0 -1
- package/dist/commands/inspect/set-work-status.d.mts +0 -1
- package/dist/commands/inspect/set-work-status.mjs +0 -29
- package/dist/commands/inspect/set-work-status.mjs.map +0 -1
- package/dist/commands/inspect/stage-map.d.mts +0 -1
- package/dist/commands/inspect/stage-map.mjs +0 -131
- package/dist/commands/inspect/stage-map.mjs.map +0 -1
- package/dist/commands/inspect/task-list.d.mts +0 -1
- package/dist/commands/inspect/task-list.mjs +0 -149
- package/dist/commands/inspect/task-list.mjs.map +0 -1
- package/dist/commands/inspect/task-show.d.mts +0 -1
- package/dist/commands/inspect/task-show.mjs +0 -108
- package/dist/commands/inspect/task-show.mjs.map +0 -1
- package/dist/commands/inspect/time-report.d.mts +0 -1
- package/dist/commands/inspect/time-report.mjs +0 -24
- package/dist/commands/inspect/time-report.mjs.map +0 -1
- package/dist/commands/inspect/usage-report.d.mts +0 -1
- package/dist/commands/inspect/usage-report.mjs +0 -23
- package/dist/commands/inspect/usage-report.mjs.map +0 -1
- package/dist/commands/inspect/user-response.d.mts +0 -1
- package/dist/commands/inspect/user-response.mjs +0 -35
- package/dist/commands/inspect/user-response.mjs.map +0 -1
- package/dist/commands/inspect/worker-liveness.d.mts +0 -1
- package/dist/commands/inspect/worker-liveness.mjs +0 -45
- package/dist/commands/inspect/worker-liveness.mjs.map +0 -1
- package/dist/commands/lifecycle/contract-check.d.mts +0 -1
- package/dist/commands/lifecycle/contract-check.mjs +0 -18
- package/dist/commands/lifecycle/contract-check.mjs.map +0 -1
- package/dist/commands/lifecycle/migrate.d.mts +0 -1
- package/dist/commands/lifecycle/migrate.mjs +0 -30
- package/dist/commands/lifecycle/migrate.mjs.map +0 -1
- package/dist/commands/lifecycle/model.d.mts +0 -1
- package/dist/commands/lifecycle/model.mjs +0 -22
- package/dist/commands/lifecycle/model.mjs.map +0 -1
- package/dist/commands/manager.d.mts +0 -3
- package/dist/commands/manager.mjs +0 -50
- package/dist/commands/manager.mjs.map +0 -1
- package/dist/commands/report/agent-activity.d.mts +0 -1
- package/dist/commands/report/agent-activity.mjs +0 -20
- package/dist/commands/report/agent-activity.mjs.map +0 -1
- package/dist/commands/report/approval-decision.d.mts +0 -1
- package/dist/commands/report/approval-decision.mjs +0 -21
- package/dist/commands/report/approval-decision.mjs.map +0 -1
- package/dist/commands/report/design-snapshot.d.mts +0 -1
- package/dist/commands/report/design-snapshot.mjs +0 -19
- package/dist/commands/report/design-snapshot.mjs.map +0 -1
- package/dist/commands/report/finalize.d.mts +0 -4
- package/dist/commands/report/finalize.mjs +0 -64
- package/dist/commands/report/finalize.mjs.map +0 -1
- package/dist/commands/report/inject-report-index.d.mts +0 -1
- package/dist/commands/report/inject-report-index.mjs +0 -21
- package/dist/commands/report/inject-report-index.mjs.map +0 -1
- package/dist/commands/report/render-final-report.d.mts +0 -1
- package/dist/commands/report/render-final-report.mjs +0 -23
- package/dist/commands/report/render-final-report.mjs.map +0 -1
- package/dist/commands/report/render-views.d.mts +0 -1
- package/dist/commands/report/render-views.mjs +0 -27
- package/dist/commands/report/render-views.mjs.map +0 -1
- package/dist/commands/report/translate.d.mts +0 -1
- package/dist/commands/report/translate.mjs +0 -33
- package/dist/commands/report/translate.mjs.map +0 -1
- package/runtime/bin/lib/okstra/tmux-pane.sh +0 -40
- package/runtime/bin/lib/okstra-ctl/cmd-batch.sh +0 -59
- package/runtime/bin/lib/okstra-ctl/cmd-list.sh +0 -35
- package/runtime/bin/lib/okstra-ctl/cmd-open.sh +0 -36
- package/runtime/bin/lib/okstra-ctl/cmd-projects.sh +0 -26
- package/runtime/bin/lib/okstra-ctl/cmd-reconcile.sh +0 -29
- package/runtime/bin/lib/okstra-ctl/cmd-reindex.sh +0 -38
- package/runtime/bin/lib/okstra-ctl/cmd-rerun.sh +0 -345
- package/runtime/bin/lib/okstra-ctl/cmd-show.sh +0 -27
- package/runtime/bin/lib/okstra-ctl/cmd-tail.sh +0 -92
- package/runtime/bin/lib/okstra-ctl/main.sh +0 -41
- package/runtime/bin/lib/okstra-ctl/prepare.sh +0 -31
- package/runtime/bin/lib/okstra-ctl/usage.sh +0 -23
- package/runtime/bin/okstra-central.sh +0 -152
- package/runtime/bin/okstra-incremental-carry.py +0 -10
- package/runtime/bin/okstra-incremental-scope.py +0 -10
- package/runtime/bin/okstra-team-reconcile.sh +0 -36
- package/runtime/python/okstra_ctl/agent_prompt_cli.py +0 -1156
- package/runtime/python/okstra_ctl/batch.py +0 -60
- package/runtime/python/okstra_ctl/clarification_items.py +0 -1050
- package/runtime/python/okstra_ctl/container_registry.py +0 -72
- package/runtime/python/okstra_ctl/improvement_assignment.py +0 -61
- package/runtime/python/okstra_ctl/resolver.py +0 -54
- package/runtime/python/okstra_ctl/team_reconcile.py +0 -275
- package/runtime/python/okstra_ctl/tmux.py +0 -134
- package/runtime/python/okstra_ctl/worktree.py +0 -1099
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"""worktree 가 쓰는 git 명령 한 겹.
|
|
2
|
+
|
|
3
|
+
`_git` 은 `check=False` 라서 호출부가 returncode 를 보고 판단한다. 이 층은
|
|
4
|
+
okstra 의 규칙을 하나도 모른다 — 브랜치가 존재하는지, HEAD 가 무엇인지,
|
|
5
|
+
머지가 충돌했는지 같은 git 사실만 답한다. okstra 가 무엇을 무시하기로 했는지는
|
|
6
|
+
`cleanliness` 가, 무엇을 옮기기로 했는지는 `sync_config` 가 안다.
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import subprocess
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _git(cwd: Path, *args: str) -> subprocess.CompletedProcess:
|
|
15
|
+
return subprocess.run(
|
|
16
|
+
["git", "-C", str(cwd), *args],
|
|
17
|
+
capture_output=True, text=True, check=False,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _is_inside_non_main_worktree(project_root: Path) -> bool:
|
|
22
|
+
"""True iff project_root is inside a git worktree that is NOT the
|
|
23
|
+
repository's main checkout. Detection rule: `--git-dir` (per-worktree
|
|
24
|
+
.git pointer) differs from `--git-common-dir` (shared object store).
|
|
25
|
+
"""
|
|
26
|
+
common = _git(project_root, "rev-parse", "--git-common-dir")
|
|
27
|
+
per_tree = _git(project_root, "rev-parse", "--git-dir")
|
|
28
|
+
if common.returncode != 0 or per_tree.returncode != 0:
|
|
29
|
+
return False
|
|
30
|
+
common_abs = (project_root / common.stdout.strip()).resolve()
|
|
31
|
+
per_tree_abs = (project_root / per_tree.stdout.strip()).resolve()
|
|
32
|
+
return common_abs != per_tree_abs
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def is_git_work_tree(project_root: Path) -> bool:
|
|
36
|
+
"""project_root 가 git work tree 내부인지 판정하는 public git-introspection seam.
|
|
37
|
+
|
|
38
|
+
git 미설치(FileNotFoundError) 를 포함한 모든 실패는 False — 호출자(migrate
|
|
39
|
+
등)가 non-git 레이아웃으로 안전하게 폴백할 수 있도록 한다. 과거 migrate.py
|
|
40
|
+
가 같은 판정을 자체 구현(`--show-toplevel`)했는데 이 seam 으로 통합한다."""
|
|
41
|
+
try:
|
|
42
|
+
res = _git(project_root, "rev-parse", "--is-inside-work-tree")
|
|
43
|
+
except (OSError, FileNotFoundError):
|
|
44
|
+
return False
|
|
45
|
+
return res.returncode == 0 and res.stdout.strip() == "true"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _branch_exists(project_root: Path, branch: str) -> bool:
|
|
49
|
+
res = _git(project_root, "rev-parse", "--verify", "--quiet", f"refs/heads/{branch}")
|
|
50
|
+
return res.returncode == 0
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _branch_checkout_path(project_root: Path, branch: str) -> str:
|
|
54
|
+
"""The worktree that currently has *branch* checked out, or "" when none does.
|
|
55
|
+
|
|
56
|
+
`git branch -D` refuses to delete a checked-out branch, so an existing-branch
|
|
57
|
+
error that only recommends `-D` is unfollowable whenever the branch is the
|
|
58
|
+
main checkout's HEAD. Callers use this to name the extra step instead.
|
|
59
|
+
"""
|
|
60
|
+
res = _git(project_root, "worktree", "list", "--porcelain")
|
|
61
|
+
if res.returncode != 0:
|
|
62
|
+
return ""
|
|
63
|
+
current = ""
|
|
64
|
+
for line in res.stdout.splitlines():
|
|
65
|
+
if line.startswith("worktree "):
|
|
66
|
+
current = line[len("worktree "):].strip()
|
|
67
|
+
elif line.strip() == f"branch refs/heads/{branch}":
|
|
68
|
+
return current
|
|
69
|
+
return ""
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _branch_exists_message(project_root: Path, branch: str, kind: str) -> str:
|
|
73
|
+
"""Existing-branch error text whose remedy is actually runnable."""
|
|
74
|
+
checkout = _branch_checkout_path(project_root, branch)
|
|
75
|
+
if checkout:
|
|
76
|
+
return (
|
|
77
|
+
f"{kind} worktree branch already exists: {branch}, and it is checked "
|
|
78
|
+
f"out at {checkout}. `git branch -D {branch}` will refuse while it is "
|
|
79
|
+
f"checked out — switch that checkout to another branch first "
|
|
80
|
+
f"(`git -C {checkout} switch <other-branch>`), then delete it, or "
|
|
81
|
+
"choose a different work-category before retrying."
|
|
82
|
+
)
|
|
83
|
+
return (
|
|
84
|
+
f"{kind} worktree branch already exists: {branch}. "
|
|
85
|
+
f"Delete it (`git branch -D {branch}`) or choose a different "
|
|
86
|
+
"work-category before retrying."
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _head_sha(cwd: Path) -> str:
|
|
91
|
+
res = _git(cwd, "rev-parse", "HEAD")
|
|
92
|
+
if res.returncode != 0:
|
|
93
|
+
return ""
|
|
94
|
+
return res.stdout.strip()
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _resolve_commit_sha(cwd: Path, ref: str) -> str:
|
|
98
|
+
"""Resolve a user-supplied ref (branch, tag, short/long SHA) to a full
|
|
99
|
+
commit SHA in `cwd`. Returns empty string when the ref is not resolvable
|
|
100
|
+
so the caller can raise a contextual error.
|
|
101
|
+
"""
|
|
102
|
+
res = _git(cwd, "rev-parse", "--verify", "--quiet", f"{ref}^{{commit}}")
|
|
103
|
+
if res.returncode != 0:
|
|
104
|
+
return ""
|
|
105
|
+
return res.stdout.strip()
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def main_worktree_path(project_root: Path) -> Path:
|
|
109
|
+
"""Locate the repository's MAIN worktree (the original checkout).
|
|
110
|
+
|
|
111
|
+
`git worktree list --porcelain` lists worktrees in a stable order
|
|
112
|
+
where the first `worktree <path>` block is the main checkout.
|
|
113
|
+
Falls back to `project_root` if parsing fails — caller still gets
|
|
114
|
+
a working path, sync-dir links just point at the caller's tree
|
|
115
|
+
(the prior behaviour).
|
|
116
|
+
"""
|
|
117
|
+
res = _git(project_root, "worktree", "list", "--porcelain")
|
|
118
|
+
if res.returncode != 0:
|
|
119
|
+
return project_root
|
|
120
|
+
for line in res.stdout.splitlines():
|
|
121
|
+
if line.startswith("worktree "):
|
|
122
|
+
return Path(line[len("worktree "):].strip())
|
|
123
|
+
return project_root
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def is_ancestor(cwd, commit: str, head: str) -> bool:
|
|
127
|
+
"""True iff `commit` is an ancestor of `head` (both non-empty)."""
|
|
128
|
+
if not commit or not head:
|
|
129
|
+
return False
|
|
130
|
+
return _git(Path(cwd), "merge-base", "--is-ancestor", commit, head).returncode == 0
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
class MergeError(RuntimeError):
|
|
134
|
+
"""`git merge` 가 내용 충돌이 아닌 사유로 실패했을 때(추적되지 않은 파일
|
|
135
|
+
덮어쓰기, 진행 중인 머지 잔존, 없는 브랜치 등) 던진다. 충돌 파일이 0개인
|
|
136
|
+
실패를 '충돌'로 둔갑시키지 않고 git stderr 를 그대로 노출해 호출자가 올바른
|
|
137
|
+
복구 경로로 안내하도록 한다."""
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def merge_branch(cwd, branch: str, *, no_ff: bool) -> list[str] | None:
|
|
141
|
+
"""`branch` 를 cwd 의 현재 HEAD 에 머지한다. 성공이면 None, 내용 충돌이면
|
|
142
|
+
abort 후 충돌 파일 목록을 반환한다(최소 1개). 충돌이 아닌 머지 실패는
|
|
143
|
+
MergeError 로 던진다 — 충돌 0개를 충돌로 보고하지 않기 위함. 호출자가 충돌
|
|
144
|
+
목록을 HandoffConflict/IntegrateError 등 적절한 예외로 변환한다 — 머지·
|
|
145
|
+
충돌감지·abort 시퀀스의 단일 지점."""
|
|
146
|
+
args = ["merge", "--no-ff", "--no-edit", branch] if no_ff \
|
|
147
|
+
else ["merge", "--no-edit", branch]
|
|
148
|
+
res = _git(Path(cwd), *args)
|
|
149
|
+
if res.returncode == 0:
|
|
150
|
+
return None
|
|
151
|
+
conflicts = _git(Path(cwd), "diff", "--name-only",
|
|
152
|
+
"--diff-filter=U").stdout.split()
|
|
153
|
+
_git(Path(cwd), "merge", "--abort")
|
|
154
|
+
if not conflicts:
|
|
155
|
+
raise MergeError((res.stderr or res.stdout).strip()
|
|
156
|
+
or f"git merge {branch} failed (exit={res.returncode})")
|
|
157
|
+
return conflicts
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def remove_worktree_force(cwd, worktree_path) -> subprocess.CompletedProcess:
|
|
161
|
+
"""워크트리를 강제 제거한다. provision_*_worktree 가 워크트리에 .okstra·
|
|
162
|
+
synced 디렉터리/파일 symlink(untracked) 를 깔기 때문에 plain remove 는 그
|
|
163
|
+
잔여물 때문에 git 이 거부한다 — dirty 게이트로 실사용자 변경을 막은 뒤
|
|
164
|
+
--force 로 okstra 잔여물만 정리하는 단일 지점."""
|
|
165
|
+
return _git(Path(cwd), "worktree", "remove", "--force", str(worktree_path))
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
"""메인 체크아웃의 공유 상태를 새 worktree 안에 재현한다.
|
|
2
|
+
|
|
3
|
+
`sync_config` 가 고른 목록을 실제로 심링크(또는 스냅샷 복사)하는 층이다. 방식이
|
|
4
|
+
세 가지이고 이유가 각각 다르다 — 디렉터리 통째 심링크는 새로 생기는 최상위
|
|
5
|
+
항목이 메인으로 가게 하고, 자식별 심링크는 git 이 심링크된 디렉터리를 파일
|
|
6
|
+
하나로 보는 탓에 `.claude` 가 `?? .claude` 로 뜨는 것을 막으며, 스냅샷 복사는
|
|
7
|
+
쓰기 핸들을 공유하지 않으려는 픽스처용이다.
|
|
8
|
+
"""
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import os
|
|
12
|
+
import shutil
|
|
13
|
+
import stat
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
from ..seeding import (
|
|
17
|
+
SettingsLinkError,
|
|
18
|
+
ensure_project_settings_symlink,
|
|
19
|
+
)
|
|
20
|
+
from .sync_config import (
|
|
21
|
+
CHILD_LINKED_SYNC_DIRS,
|
|
22
|
+
_resolve_snapshot_files,
|
|
23
|
+
_resolve_sync_dirs,
|
|
24
|
+
_resolve_sync_files,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _link_sync_dirs(source_root: Path, worktree_path: Path) -> list[str]:
|
|
29
|
+
"""Symlink each configured dir from `source_root` (the MAIN
|
|
30
|
+
worktree) into the new worktree.
|
|
31
|
+
|
|
32
|
+
Skip rules:
|
|
33
|
+
- Source missing in `source_root` → silently skipped.
|
|
34
|
+
- Target path already exists in worktree (e.g. tracked content
|
|
35
|
+
checked out by `git worktree add`) → skipped to avoid clobbering
|
|
36
|
+
version-controlled files.
|
|
37
|
+
- Parent directories are created as needed for nested entries.
|
|
38
|
+
|
|
39
|
+
Entries listed in `CHILD_LINKED_SYNC_DIRS` are materialised as a real
|
|
40
|
+
directory of per-child symlinks instead (see that constant).
|
|
41
|
+
|
|
42
|
+
Returns a list of human-readable notes (one per linked entry) so the
|
|
43
|
+
caller can include them in the provisioning note.
|
|
44
|
+
"""
|
|
45
|
+
notes: list[str] = []
|
|
46
|
+
for rel in _resolve_sync_dirs(source_root):
|
|
47
|
+
src = (source_root / rel).resolve()
|
|
48
|
+
if not src.exists():
|
|
49
|
+
continue
|
|
50
|
+
dst = worktree_path / rel
|
|
51
|
+
if dst.exists() or dst.is_symlink():
|
|
52
|
+
continue
|
|
53
|
+
dst.parent.mkdir(parents=True, exist_ok=True)
|
|
54
|
+
if rel in CHILD_LINKED_SYNC_DIRS and src.is_dir():
|
|
55
|
+
_link_dir_children(src, dst)
|
|
56
|
+
notes.append(rel)
|
|
57
|
+
continue
|
|
58
|
+
try:
|
|
59
|
+
os.symlink(src, dst)
|
|
60
|
+
except FileExistsError:
|
|
61
|
+
continue
|
|
62
|
+
notes.append(rel)
|
|
63
|
+
return notes
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _link_dir_children(src: Path, dst: Path) -> None:
|
|
67
|
+
"""Create `dst` as a real directory whose entries symlink to `src`'s
|
|
68
|
+
children, so git sees the same directory shape it sees in the main
|
|
69
|
+
checkout (`CHILD_LINKED_SYNC_DIRS`).
|
|
70
|
+
|
|
71
|
+
Each link points at the MAIN checkout's child rather than its resolved
|
|
72
|
+
target, so a child that is itself a symlink (e.g. `.claude/settings.local.json`
|
|
73
|
+
→ `~/.okstra/templates/settings.local.json`) keeps following whatever the
|
|
74
|
+
main checkout currently points at. An unreadable source or a child that
|
|
75
|
+
cannot be linked degrades to "that child is absent in this worktree" —
|
|
76
|
+
provisioning is not worth failing over host config.
|
|
77
|
+
"""
|
|
78
|
+
dst.mkdir(parents=True, exist_ok=True)
|
|
79
|
+
try:
|
|
80
|
+
children = sorted(src.iterdir())
|
|
81
|
+
except OSError:
|
|
82
|
+
return
|
|
83
|
+
for child in children:
|
|
84
|
+
link = dst / child.name
|
|
85
|
+
if link.exists() or link.is_symlink():
|
|
86
|
+
continue
|
|
87
|
+
try:
|
|
88
|
+
os.symlink(child, link)
|
|
89
|
+
except OSError:
|
|
90
|
+
continue
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _link_sync_files(source_root: Path, worktree_path: Path) -> list[str]:
|
|
94
|
+
"""File-level counterpart to `_link_sync_dirs` (FU-V2).
|
|
95
|
+
|
|
96
|
+
Same skip rules: missing source → skipped silently; pre-existing dst
|
|
97
|
+
(tracked content checked out by `git worktree add`) → skipped to avoid
|
|
98
|
+
clobbering. Symlinks (not copies) keep secrets out of duplicate files —
|
|
99
|
+
the link points back to the MAIN worktree's `.env` so rotating the file
|
|
100
|
+
there is reflected in every active task without re-provisioning.
|
|
101
|
+
"""
|
|
102
|
+
notes: list[str] = []
|
|
103
|
+
for rel in _resolve_sync_files(source_root):
|
|
104
|
+
src = (source_root / rel).resolve()
|
|
105
|
+
if not src.exists() or not src.is_file():
|
|
106
|
+
continue
|
|
107
|
+
dst = worktree_path / rel
|
|
108
|
+
if dst.exists() or dst.is_symlink():
|
|
109
|
+
continue
|
|
110
|
+
dst.parent.mkdir(parents=True, exist_ok=True)
|
|
111
|
+
try:
|
|
112
|
+
os.symlink(src, dst)
|
|
113
|
+
except FileExistsError:
|
|
114
|
+
continue
|
|
115
|
+
notes.append(rel)
|
|
116
|
+
return notes
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def sync_file_key_names(source_root: Path) -> dict[str, list[str]]:
|
|
120
|
+
"""공유되는 sync 파일마다 그 안의 **키 이름**만 모은다 (값은 절대 읽지 않는다).
|
|
121
|
+
|
|
122
|
+
`.env` 는 gitignore 대상이라 브랜치에 속하지 않고, `_link_sync_files` 가 메인
|
|
123
|
+
체크아웃의 것을 각 worktree 에 심링크로 건다. 값 회전은 그 설계가 노린 것이지만
|
|
124
|
+
**키 이름 변경**은 아직 옛 이름을 읽는 모든 브랜치를 한꺼번에 깨뜨린다 — 코드는
|
|
125
|
+
브랜치별로 격리되지만 `.env` 는 격리되지 않기 때문이다. 그 사고를 알아보려면
|
|
126
|
+
프로비저닝 시점의 이름 집합을 남겨야 한다.
|
|
127
|
+
|
|
128
|
+
`KEY=` 꼴이 아닌 파일은 빈 목록이 되어 드리프트 판정에서 자연히 빠진다 —
|
|
129
|
+
sync 파일 목록은 프로젝트가 바꿀 수 있으므로 `.env` 형식을 전제하지 않는다.
|
|
130
|
+
"""
|
|
131
|
+
keys: dict[str, list[str]] = {}
|
|
132
|
+
for rel in _resolve_sync_files(source_root):
|
|
133
|
+
src = (source_root / rel).resolve()
|
|
134
|
+
if not src.is_file():
|
|
135
|
+
continue
|
|
136
|
+
try:
|
|
137
|
+
text = src.read_text(encoding="utf-8")
|
|
138
|
+
except (OSError, UnicodeDecodeError):
|
|
139
|
+
continue
|
|
140
|
+
found = []
|
|
141
|
+
for line in text.splitlines():
|
|
142
|
+
stripped = line.strip().removeprefix("export ").lstrip()
|
|
143
|
+
if not stripped or stripped.startswith("#"):
|
|
144
|
+
continue
|
|
145
|
+
name, separator, _ = stripped.partition("=")
|
|
146
|
+
name = name.strip()
|
|
147
|
+
if separator and name and all(
|
|
148
|
+
ch.isalnum() or ch == "_" for ch in name
|
|
149
|
+
):
|
|
150
|
+
found.append(name)
|
|
151
|
+
keys[rel] = sorted(set(found))
|
|
152
|
+
return keys
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def describe_sync_key_drift(
|
|
156
|
+
recorded: object, current: dict[str, list[str]]
|
|
157
|
+
) -> str:
|
|
158
|
+
"""프로비저닝 때 기록한 키 이름 집합과 지금을 대조해 한 줄로 알린다.
|
|
159
|
+
|
|
160
|
+
기록이 없으면(그 전에 만들어진 worktree) 빈 문자열 — 알 수 없는 것을 사고로
|
|
161
|
+
보고하지 않는다. 차단이 아니라 알림인 이유는 키 추가가 정상 작업이기 때문이다.
|
|
162
|
+
"""
|
|
163
|
+
if not isinstance(recorded, dict):
|
|
164
|
+
return ""
|
|
165
|
+
parts: list[str] = []
|
|
166
|
+
for rel in sorted(set(recorded) | set(current)):
|
|
167
|
+
before = set(recorded.get(rel) or [])
|
|
168
|
+
after = set(current.get(rel) or [])
|
|
169
|
+
removed = sorted(before - after)
|
|
170
|
+
added = sorted(after - before)
|
|
171
|
+
if not removed and not added:
|
|
172
|
+
continue
|
|
173
|
+
detail = []
|
|
174
|
+
if removed:
|
|
175
|
+
detail.append(f"gone: {', '.join(removed)}")
|
|
176
|
+
if added:
|
|
177
|
+
detail.append(f"new: {', '.join(added)}")
|
|
178
|
+
parts.append(f"{rel} ({'; '.join(detail)})")
|
|
179
|
+
if not parts:
|
|
180
|
+
return ""
|
|
181
|
+
return (
|
|
182
|
+
"shared-env drift — " + "; ".join(parts) + ". This file is symlinked "
|
|
183
|
+
"from the main checkout into every task worktree, so a renamed key "
|
|
184
|
+
"breaks every branch still reading the old name at once, and it shows "
|
|
185
|
+
"up as verifier sessions failing to boot while the executor passes. "
|
|
186
|
+
"Key names only; values are never recorded."
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def _seed_worktree_settings_symlink(worktree_path: Path) -> None:
|
|
191
|
+
"""Seed `.claude/settings.local.json` in the worker worktree so dispatched
|
|
192
|
+
Claude / codex / antigravity sessions inherit the okstra read-only / write
|
|
193
|
+
allowlist. Mirrors the main-project seeding done in `run.py` — needed
|
|
194
|
+
because `_link_sync_dirs` skips `.claude/` whenever `git worktree add`
|
|
195
|
+
already materialised the directory (e.g. tracked `.claude/handoff-*.md`).
|
|
196
|
+
Failures degrade to stderr warning so worktree provisioning still
|
|
197
|
+
succeeds.
|
|
198
|
+
"""
|
|
199
|
+
try:
|
|
200
|
+
link = ensure_project_settings_symlink(project_root=worktree_path)
|
|
201
|
+
except SettingsLinkError as exc:
|
|
202
|
+
print(
|
|
203
|
+
f"okstra-settings: failed to seed worker worktree symlink at "
|
|
204
|
+
f"{worktree_path / '.claude/settings.local.json'} — worker dispatch "
|
|
205
|
+
f"may be blocked by Claude Code permissions. ({exc})",
|
|
206
|
+
file=__import__("sys").stderr,
|
|
207
|
+
)
|
|
208
|
+
return
|
|
209
|
+
if link is None:
|
|
210
|
+
print(
|
|
211
|
+
"okstra-settings: ~/.okstra/templates/settings.local.json missing — "
|
|
212
|
+
"re-run 'npx okstra@latest install' (0.14.0+) to provision the "
|
|
213
|
+
"symlink target.",
|
|
214
|
+
file=__import__("sys").stderr,
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def _copy_snapshot_files(source_root: Path, worktree_path: Path) -> list[str]:
|
|
219
|
+
"""Copy fixture files from MAIN → task worktree as read-only snapshots
|
|
220
|
+
(FU-V3).
|
|
221
|
+
|
|
222
|
+
Unlike `_link_sync_files`, this materialises a fresh on-disk copy and
|
|
223
|
+
chmods it to `0o444` so the verifier can read the same rows the
|
|
224
|
+
executor saw without sharing a writable handle that would corrupt the
|
|
225
|
+
main worktree's copy. Skip rules mirror the symlink helpers (missing
|
|
226
|
+
source → skipped silently; pre-existing dst → skipped to avoid
|
|
227
|
+
clobbering tracked content).
|
|
228
|
+
"""
|
|
229
|
+
notes: list[str] = []
|
|
230
|
+
for rel in _resolve_snapshot_files(source_root):
|
|
231
|
+
src = (source_root / rel).resolve()
|
|
232
|
+
if not src.exists() or not src.is_file():
|
|
233
|
+
continue
|
|
234
|
+
dst = worktree_path / rel
|
|
235
|
+
if dst.exists() or dst.is_symlink():
|
|
236
|
+
continue
|
|
237
|
+
dst.parent.mkdir(parents=True, exist_ok=True)
|
|
238
|
+
shutil.copy2(src, dst)
|
|
239
|
+
try:
|
|
240
|
+
os.chmod(dst, stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH)
|
|
241
|
+
except OSError:
|
|
242
|
+
# chmod failure (exotic filesystems, root-squashed mounts) does
|
|
243
|
+
# not invalidate the snapshot itself — it just means the read-
|
|
244
|
+
# only contract is best-effort. Surface in notes so the operator
|
|
245
|
+
# can audit if a verifier later mutates the file.
|
|
246
|
+
notes.append(f"{rel} (rw-fallback)")
|
|
247
|
+
continue
|
|
248
|
+
notes.append(rel)
|
|
249
|
+
return notes
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""task-key 를 worktree 경로와 브랜치 이름으로 옮긴다.
|
|
2
|
+
|
|
3
|
+
경로도 브랜치도 파일 시스템과 git 참조 양쪽에서 안전한 문자만 남겨야 하고,
|
|
4
|
+
브랜치는 work-category 별 namespace 아래로 들어간다. 순수 문자열 계산이라
|
|
5
|
+
디스크도 git 도 보지 않는다 — 그래서 provision 전에 미리 보여 줄 수 있다.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
from okstra_project.dirs import okstra_home
|
|
12
|
+
|
|
13
|
+
from ..ids import _safe_fs_segment
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# Work-category → branch namespace (slash-prefixed). Mirrors the values
|
|
17
|
+
# accepted by `--work-category` (bugfix / feature / refactor / ops /
|
|
18
|
+
# improvement); `feature` and `improvement` share the `feature/` namespace,
|
|
19
|
+
# and any unset/unrecognised category falls back to `task/`.
|
|
20
|
+
_WORK_CATEGORY_NAMESPACE = {
|
|
21
|
+
"feature": "feature",
|
|
22
|
+
"improvement": "feature",
|
|
23
|
+
"bugfix": "fix",
|
|
24
|
+
"refactor": "refactor",
|
|
25
|
+
"ops": "ops",
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _safe_segment(value: str) -> str:
|
|
30
|
+
"""Sanitise a single path/branch segment.
|
|
31
|
+
|
|
32
|
+
Forbidden chars (`/`, `:`, spaces, anything outside `[a-z0-9-]`)
|
|
33
|
+
are collapsed to `-`. Empty result becomes `_` so we never create
|
|
34
|
+
an empty path component. Delegates to the canonical slugifier in
|
|
35
|
+
`ids.py` to stay in lock-step with run-id / manifest segmentation.
|
|
36
|
+
"""
|
|
37
|
+
return _safe_fs_segment(value)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _work_category_namespace(work_category: str) -> str:
|
|
41
|
+
key = (work_category or "").strip().lower()
|
|
42
|
+
return _WORK_CATEGORY_NAMESPACE.get(key, "task")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def compute_worktree_path(
|
|
46
|
+
*,
|
|
47
|
+
project_id: str,
|
|
48
|
+
task_group_segment: str,
|
|
49
|
+
task_id_segment: str,
|
|
50
|
+
stage_number: Optional[int] = None,
|
|
51
|
+
group_id: Optional[str] = None,
|
|
52
|
+
) -> Path:
|
|
53
|
+
"""Pure path computation. One worktree dir per task-key, or per
|
|
54
|
+
`<task-key>/stage-<N>` when stage_number is given (implementation
|
|
55
|
+
stage isolation). Uses `OKSTRA_HOME` when set (test hook), else
|
|
56
|
+
`~/.okstra`."""
|
|
57
|
+
if stage_number is not None and group_id is not None:
|
|
58
|
+
raise ValueError("stage_number and group_id are mutually exclusive")
|
|
59
|
+
base = okstra_home()
|
|
60
|
+
path = (
|
|
61
|
+
base / "worktrees"
|
|
62
|
+
/ _safe_segment(project_id)
|
|
63
|
+
/ _safe_segment(task_group_segment)
|
|
64
|
+
/ _safe_segment(task_id_segment)
|
|
65
|
+
)
|
|
66
|
+
if stage_number is not None:
|
|
67
|
+
path = path / f"stage-{stage_number}"
|
|
68
|
+
if group_id is not None:
|
|
69
|
+
path = path / f"group-{group_id}"
|
|
70
|
+
return path
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def compute_branch_name(
|
|
74
|
+
*,
|
|
75
|
+
work_category: str,
|
|
76
|
+
task_id_segment: str,
|
|
77
|
+
stage_number: Optional[int] = None,
|
|
78
|
+
group_id: Optional[str] = None,
|
|
79
|
+
) -> str:
|
|
80
|
+
"""One branch per task-key as `<namespace>/<task-id>`, or
|
|
81
|
+
`<namespace>/<task-id>-s<N>` for an implementation stage worktree. The
|
|
82
|
+
namespace is a controlled constant so its slash is preserved; only the
|
|
83
|
+
task-id segment is sanitised."""
|
|
84
|
+
if stage_number is not None and group_id is not None:
|
|
85
|
+
raise ValueError("stage_number and group_id are mutually exclusive")
|
|
86
|
+
name = f"{_work_category_namespace(work_category)}/{_safe_segment(task_id_segment)}"
|
|
87
|
+
if stage_number is not None:
|
|
88
|
+
name = f"{name}-s{stage_number}"
|
|
89
|
+
if group_id is not None:
|
|
90
|
+
name = f"{name}-{group_id}"
|
|
91
|
+
return name
|