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
|
@@ -4,7 +4,8 @@ from __future__ import annotations
|
|
|
4
4
|
import json
|
|
5
5
|
import os
|
|
6
6
|
import tempfile
|
|
7
|
-
from collections.abc import Mapping, Sequence
|
|
7
|
+
from collections.abc import Mapping, MutableMapping, Sequence
|
|
8
|
+
from dataclasses import replace
|
|
8
9
|
from functools import lru_cache
|
|
9
10
|
from pathlib import Path
|
|
10
11
|
from typing import Any, Callable
|
|
@@ -13,6 +14,7 @@ from .domain.provider import HostModelBinding, ServedModelAttestation
|
|
|
13
14
|
from .domain.worker_exec import WorkerWriteCapability
|
|
14
15
|
from .execution_identity import (
|
|
15
16
|
Attempt,
|
|
17
|
+
build_dispatch_id,
|
|
16
18
|
EXECUTION_IDENTITY_VERSION,
|
|
17
19
|
ExecutionManifest,
|
|
18
20
|
ExecutionManifestError,
|
|
@@ -62,10 +64,50 @@ def read_execution_manifest_view(
|
|
|
62
64
|
surface = read_identity_surface(path, surface="run-manifest")
|
|
63
65
|
if surface.legacy:
|
|
64
66
|
return synthesize_v1_execution_view(surface.payload), surface.payload
|
|
67
|
+
hoist_legacy_attempt_write_contract(surface.payload)
|
|
65
68
|
validate_execution_manifest_payload(surface.payload)
|
|
66
69
|
return _execution_manifest_from_payload(surface.payload), surface.payload
|
|
67
70
|
|
|
68
71
|
|
|
72
|
+
WRITE_CONTRACT_KEYS = ("writePolicy", "writePolicyDigest", "writeEnforcement")
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def hoist_legacy_attempt_write_contract(payload: Any) -> None:
|
|
76
|
+
"""쓰기 계약이 invocation 에 실린 매니페스트를 attempt 소유로 옮긴다.
|
|
77
|
+
|
|
78
|
+
계약은 attempt 마다 다르다(`execution_identity.Attempt` 참고). 그 이전에
|
|
79
|
+
쓰인 매니페스트는 계약을 invocation 행에 담고 attempt 행에는 안 담으므로,
|
|
80
|
+
새 검증기가 보면 attempt 가 계약 없는 행이 된다 — 진행 중인 태스크가 전부
|
|
81
|
+
그 상태다. 여기서 옮겨 두면 다음 쓰기에서 파일 자체가 새 모양이 된다.
|
|
82
|
+
|
|
83
|
+
이 함수는 옮기기만 하고 값은 만들지 않는다. attempt 가 없는 invocation 의
|
|
84
|
+
계약은 버린다 — 그 값을 읽는 소비자는 없고, 디스패치가 attempt 를 열 때
|
|
85
|
+
다시 계산한다. 진행 중인 태스크가 모두 끝나면 통째로 지울 수 있다.
|
|
86
|
+
"""
|
|
87
|
+
if not isinstance(payload, MutableMapping):
|
|
88
|
+
return
|
|
89
|
+
invocations = payload.get("invocations")
|
|
90
|
+
attempts = payload.get("attempts")
|
|
91
|
+
if not isinstance(invocations, list):
|
|
92
|
+
return
|
|
93
|
+
carried: dict[str, dict[str, Any]] = {}
|
|
94
|
+
for row in invocations:
|
|
95
|
+
if not isinstance(row, MutableMapping) or "writePolicy" not in row:
|
|
96
|
+
continue
|
|
97
|
+
contract = {key: row.pop(key) for key in WRITE_CONTRACT_KEYS if key in row}
|
|
98
|
+
ref = row.get("invocationRef")
|
|
99
|
+
if isinstance(ref, str):
|
|
100
|
+
carried[ref] = contract
|
|
101
|
+
if not carried or not isinstance(attempts, list):
|
|
102
|
+
return
|
|
103
|
+
for row in attempts:
|
|
104
|
+
if not isinstance(row, MutableMapping) or "writePolicy" in row:
|
|
105
|
+
continue
|
|
106
|
+
contract = carried.get(row.get("invocationRef"))
|
|
107
|
+
if contract:
|
|
108
|
+
row.update(contract)
|
|
109
|
+
|
|
110
|
+
|
|
69
111
|
def write_execution_manifest(path: Path, manifest: ExecutionManifest) -> None:
|
|
70
112
|
if manifest.legacy:
|
|
71
113
|
raise ExecutionManifestError("cannot write a v1 memory view")
|
|
@@ -130,7 +172,7 @@ def _drift_detail(existing: Invocation, incoming: Invocation) -> str:
|
|
|
130
172
|
before = existing.to_payload()
|
|
131
173
|
after = incoming.to_payload()
|
|
132
174
|
changed = sorted(
|
|
133
|
-
key for key in (*before, *after) if before.get(key) != after.get(key)
|
|
175
|
+
{key for key in (*before, *after) if before.get(key) != after.get(key)}
|
|
134
176
|
)
|
|
135
177
|
return ", ".join(changed) if changed else "(no field differs)"
|
|
136
178
|
|
|
@@ -349,10 +391,8 @@ def finish_attempt_mutation(
|
|
|
349
391
|
if index is None:
|
|
350
392
|
raise ExecutionManifestError("attempt to finish is unknown")
|
|
351
393
|
existing = manifest.attempts[index]
|
|
352
|
-
closed =
|
|
353
|
-
|
|
354
|
-
attempt=attempt,
|
|
355
|
-
started_at=existing.started_at,
|
|
394
|
+
closed = replace(
|
|
395
|
+
existing,
|
|
356
396
|
finished_at=finished_at,
|
|
357
397
|
status=status,
|
|
358
398
|
result_path=result_path,
|
|
@@ -422,7 +462,7 @@ def validate_execution_manifest_payload(payload: Mapping[str, Any]) -> None:
|
|
|
422
462
|
invocations = _indexed_rows(payload["invocations"], "invocationRef")
|
|
423
463
|
_validate_roles(payload, participants)
|
|
424
464
|
_validate_invocations(participants, roles, invocations)
|
|
425
|
-
_validate_attempts(payload["attempts"], invocations)
|
|
465
|
+
_validate_attempts(payload["attempts"], invocations, participants, roles)
|
|
426
466
|
_validate_seals_and_recoveries(payload)
|
|
427
467
|
|
|
428
468
|
|
|
@@ -613,10 +653,9 @@ def _validate_invocations(
|
|
|
613
653
|
source = row.get("sourceInvocationRef")
|
|
614
654
|
if source is not None and source not in invocations:
|
|
615
655
|
raise ExecutionManifestError("invocation sourceInvocationRef is unknown")
|
|
616
|
-
_validate_invocation_write_contract(row, participants[participant_ref], roles[role_ref])
|
|
617
656
|
|
|
618
657
|
|
|
619
|
-
def
|
|
658
|
+
def _validate_attempt_write_contract(
|
|
620
659
|
row: Mapping[str, Any],
|
|
621
660
|
participant: Mapping[str, Any],
|
|
622
661
|
role: Mapping[str, Any],
|
|
@@ -624,21 +663,21 @@ def _validate_invocation_write_contract(
|
|
|
624
663
|
policy = row.get("writePolicy")
|
|
625
664
|
enforcement = row.get("writeEnforcement")
|
|
626
665
|
if not isinstance(policy, Mapping) or not isinstance(enforcement, Mapping):
|
|
627
|
-
raise ExecutionManifestError("
|
|
666
|
+
raise ExecutionManifestError("attempt write contract must be structured")
|
|
628
667
|
try:
|
|
629
668
|
validate_write_policy_payload(policy)
|
|
630
669
|
validate_write_enforcement_payload(enforcement)
|
|
631
670
|
expected_digest = write_policy_digest(policy)
|
|
632
671
|
except WritePolicyError as exc:
|
|
633
|
-
raise ExecutionManifestError(f"
|
|
672
|
+
raise ExecutionManifestError(f"attempt write contract: {exc}") from exc
|
|
634
673
|
if row.get("writePolicyDigest") != expected_digest:
|
|
635
|
-
raise ExecutionManifestError("
|
|
674
|
+
raise ExecutionManifestError("attempt writePolicyDigest does not match")
|
|
636
675
|
source = policy.get("sourcePolicy")
|
|
637
676
|
expected_mode = (
|
|
638
677
|
"project-mutation" if role.get("role") == "implementer" else "source-readonly"
|
|
639
678
|
)
|
|
640
679
|
if not isinstance(source, Mapping) or source.get("mode") != expected_mode:
|
|
641
|
-
raise ExecutionManifestError("
|
|
680
|
+
raise ExecutionManifestError("attempt sourcePolicy does not match role")
|
|
642
681
|
git_policy = policy.get("gitPolicy")
|
|
643
682
|
expected_git_mode = (
|
|
644
683
|
"fast-forward-descendant-chain"
|
|
@@ -649,10 +688,10 @@ def _validate_invocation_write_contract(
|
|
|
649
688
|
not isinstance(git_policy, Mapping)
|
|
650
689
|
or git_policy.get("mode") != expected_git_mode
|
|
651
690
|
):
|
|
652
|
-
raise ExecutionManifestError("
|
|
691
|
+
raise ExecutionManifestError("attempt gitPolicy does not match role")
|
|
653
692
|
if expected_mode == "source-readonly" and source.get("plannedPaths"):
|
|
654
693
|
raise ExecutionManifestError(
|
|
655
|
-
"source-readonly
|
|
694
|
+
"source-readonly attempt cannot declare planned paths"
|
|
656
695
|
)
|
|
657
696
|
_validate_enforcement_capability(enforcement, participant, role)
|
|
658
697
|
|
|
@@ -674,7 +713,7 @@ def _validate_enforcement_capability(
|
|
|
674
713
|
)
|
|
675
714
|
return
|
|
676
715
|
if not isinstance(capability, Mapping):
|
|
677
|
-
raise ExecutionManifestError("worker
|
|
716
|
+
raise ExecutionManifestError("worker attempt has no write capability")
|
|
678
717
|
rank = {"none": 0, "directory-boundary": 1, "exact-path": 2}
|
|
679
718
|
maximum = capability.get("maxBoundaryPrecision")
|
|
680
719
|
actual = enforcement.get("boundaryPrecision")
|
|
@@ -685,6 +724,8 @@ def _validate_enforcement_capability(
|
|
|
685
724
|
def _validate_attempts(
|
|
686
725
|
rows: object,
|
|
687
726
|
invocations: Mapping[str, Mapping[str, Any]],
|
|
727
|
+
participants: Mapping[str, Mapping[str, Any]],
|
|
728
|
+
roles: Mapping[str, Mapping[str, Any]],
|
|
688
729
|
) -> None:
|
|
689
730
|
if not isinstance(rows, list):
|
|
690
731
|
raise ExecutionManifestError("execution manifest attempts must be an array")
|
|
@@ -698,6 +739,12 @@ def _validate_attempts(
|
|
|
698
739
|
attempt = row.get("attempt")
|
|
699
740
|
if not isinstance(attempt, int) or attempt < 1:
|
|
700
741
|
raise ExecutionManifestError("attempt number must be positive")
|
|
742
|
+
invocation = invocations[invocation_ref]
|
|
743
|
+
participant = participants.get(str(invocation.get("participantRef")))
|
|
744
|
+
role = roles.get(str(invocation.get("roleExecutionRef")))
|
|
745
|
+
if participant is None or role is None:
|
|
746
|
+
raise ExecutionManifestError("attempt references unknown execution identity")
|
|
747
|
+
_validate_attempt_write_contract(row, participant, role)
|
|
701
748
|
attempts_by_invocation.setdefault(invocation_ref, []).append(attempt)
|
|
702
749
|
for invocation_ref, values in attempts_by_invocation.items():
|
|
703
750
|
if sorted(values) != list(range(1, len(values) + 1)):
|
|
@@ -710,7 +757,6 @@ def _validate_seals_and_recoveries(payload: Mapping[str, Any]) -> None:
|
|
|
710
757
|
attempts = payload.get("attempts")
|
|
711
758
|
if not isinstance(attempts, list):
|
|
712
759
|
return
|
|
713
|
-
from .dispatch_state import build_dispatch_id
|
|
714
760
|
|
|
715
761
|
attempt_refs = {
|
|
716
762
|
build_dispatch_id(str(row.get("invocationRef")), int(row.get("attempt") or 0))
|
|
@@ -925,9 +971,6 @@ def _invocation_from_payload(row: Mapping[str, Any]) -> Invocation:
|
|
|
925
971
|
dispatch_kind=str(row["dispatchKind"]),
|
|
926
972
|
round=int(row["round"]),
|
|
927
973
|
input_digest=str(row["inputDigest"]),
|
|
928
|
-
write_policy=dict(row["writePolicy"]),
|
|
929
|
-
write_policy_digest=str(row["writePolicyDigest"]),
|
|
930
|
-
write_enforcement=dict(row["writeEnforcement"]),
|
|
931
974
|
)
|
|
932
975
|
|
|
933
976
|
|
|
@@ -942,6 +985,9 @@ def _attempt_from_payload(row: Mapping[str, Any]) -> Attempt:
|
|
|
942
985
|
error_path=_optional_text(row.get("errorPath")),
|
|
943
986
|
change_summary=dict(row["changeSummary"]),
|
|
944
987
|
evidence_seal_ref=_optional_text(row.get("evidenceSealRef")),
|
|
988
|
+
write_policy=dict(row["writePolicy"]),
|
|
989
|
+
write_policy_digest=str(row["writePolicyDigest"]),
|
|
990
|
+
write_enforcement=dict(row["writeEnforcement"]),
|
|
945
991
|
)
|
|
946
992
|
|
|
947
993
|
|
|
@@ -1223,6 +1269,7 @@ def _role_execution_index_from_runs(
|
|
|
1223
1269
|
surface = read_identity_surface(resolved_path, surface="run-manifest")
|
|
1224
1270
|
if surface.legacy:
|
|
1225
1271
|
continue
|
|
1272
|
+
hoist_legacy_attempt_write_contract(surface.payload)
|
|
1226
1273
|
validate_execution_manifest_payload(surface.payload)
|
|
1227
1274
|
run_ref = resolved_path.relative_to(resolved_reference_root).as_posix()
|
|
1228
1275
|
rows.extend(
|
|
@@ -83,6 +83,10 @@ class MutationSnapshot:
|
|
|
83
83
|
class MutationAuditResult:
|
|
84
84
|
status: str
|
|
85
85
|
changed_paths: tuple[str, ...]
|
|
86
|
+
# 추적되지 않는 채로 생기거나 바뀐 경로. 소스 변경이 아니라 명령이 남긴
|
|
87
|
+
# 산출물이라 판정을 막지 않는다 — 그래도 기록은 남긴다. 없던 일로 하면
|
|
88
|
+
# 워커가 워크트리에 무엇을 남겼는지 아무도 모른다.
|
|
89
|
+
untracked_paths: tuple[str, ...]
|
|
86
90
|
source_changed: bool
|
|
87
91
|
git_changed: bool
|
|
88
92
|
attribution: str
|
|
@@ -96,6 +100,7 @@ class MutationAuditResult:
|
|
|
96
100
|
return {
|
|
97
101
|
"mutationStatus": self.status,
|
|
98
102
|
"changedPaths": list(self.changed_paths),
|
|
103
|
+
"untrackedPaths": list(self.untracked_paths),
|
|
99
104
|
"sourceChanged": self.source_changed,
|
|
100
105
|
"gitChanged": self.git_changed,
|
|
101
106
|
"attribution": self.attribution,
|
|
@@ -118,11 +123,13 @@ class ExecutionMutationAudit:
|
|
|
118
123
|
rows = tuple(policies)
|
|
119
124
|
root, artifact_root = _validate_batch(rows)
|
|
120
125
|
generated = _generated_paths(rows)
|
|
121
|
-
file_digests = _content_snapshot(root, generated)
|
|
126
|
+
file_digests = _content_snapshot(root, generated | _ignored_paths(root))
|
|
122
127
|
artifact_digests = (
|
|
123
128
|
file_digests
|
|
124
129
|
if artifact_root == root
|
|
125
|
-
else _content_snapshot(
|
|
130
|
+
else _content_snapshot(
|
|
131
|
+
artifact_root, generated | _ignored_paths(artifact_root)
|
|
132
|
+
)
|
|
126
133
|
)
|
|
127
134
|
scratch_digests = _scratch_snapshot(rows)
|
|
128
135
|
git_projection = _git_projection(root)
|
|
@@ -174,7 +181,9 @@ class ExecutionMutationAudit:
|
|
|
174
181
|
scratch_changed = _changed_keys(
|
|
175
182
|
before.scratch_digests, after.scratch_digests
|
|
176
183
|
)
|
|
177
|
-
source_changes =
|
|
184
|
+
source_changes, untracked_changes = _split_tracked(
|
|
185
|
+
before.root, _source_changes(changed, rows, before)
|
|
186
|
+
)
|
|
178
187
|
# Reported and, through `_retry_allowed`, load-bearing: a stat-cache
|
|
179
188
|
# refresh must not read as "this worker touched Git" and must not
|
|
180
189
|
# withhold the retry a worker that failed for another reason is owed.
|
|
@@ -203,6 +212,7 @@ class ExecutionMutationAudit:
|
|
|
203
212
|
return MutationAuditResult(
|
|
204
213
|
status=status,
|
|
205
214
|
changed_paths=tuple(sorted(source_changes)),
|
|
215
|
+
untracked_paths=tuple(sorted(untracked_changes)),
|
|
206
216
|
source_changed=bool(source_changes),
|
|
207
217
|
git_changed=git_changed,
|
|
208
218
|
attribution="batch-unattributed",
|
|
@@ -273,7 +283,9 @@ when a run produces the same evidence for it, not before.
|
|
|
273
283
|
"""
|
|
274
284
|
|
|
275
285
|
|
|
276
|
-
def _content_snapshot(root: Path,
|
|
286
|
+
def _content_snapshot(root: Path, excluded: frozenset[str]) -> dict[str, str]:
|
|
287
|
+
"""감사 대상 트리의 내용 다이제스트. `excluded` 는 정책이 선언한 생성
|
|
288
|
+
경로와 레포가 무시하는 경로를 합친 접두사 집합이다."""
|
|
277
289
|
rows: dict[str, str] = {}
|
|
278
290
|
for current, directories, filenames in os.walk(root, followlinks=False):
|
|
279
291
|
current_path = Path(current)
|
|
@@ -284,7 +296,7 @@ def _content_snapshot(root: Path, generated: frozenset[str]) -> dict[str, str]:
|
|
|
284
296
|
if (
|
|
285
297
|
name == ".git"
|
|
286
298
|
or name in _INSTALLED_DEPENDENCY_DIRS
|
|
287
|
-
or _excluded(relative_path,
|
|
299
|
+
or _excluded(relative_path, excluded)
|
|
288
300
|
):
|
|
289
301
|
continue
|
|
290
302
|
if path.is_symlink():
|
|
@@ -295,7 +307,7 @@ def _content_snapshot(root: Path, generated: frozenset[str]) -> dict[str, str]:
|
|
|
295
307
|
for name in sorted(filenames):
|
|
296
308
|
path = current_path / name
|
|
297
309
|
relative = path.relative_to(root).as_posix()
|
|
298
|
-
if relative == ".git" or _excluded(Path(relative),
|
|
310
|
+
if relative == ".git" or _excluded(Path(relative), excluded):
|
|
299
311
|
continue
|
|
300
312
|
rows[relative] = _path_digest(path)
|
|
301
313
|
return rows
|
|
@@ -327,9 +339,79 @@ def _path_digest(path: Path) -> str:
|
|
|
327
339
|
return "sha256:" + hashlib.sha256(value).hexdigest()
|
|
328
340
|
|
|
329
341
|
|
|
330
|
-
def _excluded(path: Path,
|
|
342
|
+
def _excluded(path: Path, excluded: frozenset[str]) -> bool:
|
|
331
343
|
relative = path.as_posix()
|
|
332
|
-
return any(relative == item or relative.startswith(item + "/") for item in
|
|
344
|
+
return any(relative == item or relative.startswith(item + "/") for item in excluded)
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
def _ignored_paths(root: Path) -> frozenset[str]:
|
|
348
|
+
"""레포가 스스로 소스가 아니라고 선언한 경로.
|
|
349
|
+
|
|
350
|
+
감사는 워커가 *소스* 를 바꿨는지를 묻는다. `.gitignore` 는 그 레포에서
|
|
351
|
+
무엇이 소스가 아닌지에 대한 사람이 쓴 답이므로, 그 답을 여기서 다시 쓴다 —
|
|
352
|
+
okstra 가 파일 이름 목록을 따로 들고 있으면 레포마다 틀린다.
|
|
353
|
+
|
|
354
|
+
이 면제가 필요한 이유는 감사 창이 사람·OS 와 공유되기 때문이다. 실측
|
|
355
|
+
(2026-08-26, `fontsninja-nlpvibe` planning run 019~022): 완주한 워커의 결과가
|
|
356
|
+
버려진 10건 중 2건의 유발자가 Finder 가 쓴 `.DS_Store` 와 IDE 가 쓴
|
|
357
|
+
`.idea/workspace.xml` 이었다. 어느 쪽도 워커가 쓴 것이 아니고, 둘 다 그
|
|
358
|
+
레포의 무시 대상이다.
|
|
359
|
+
|
|
360
|
+
추적 중인 파일은 git 이 무시 대상으로 세지 않으므로 그대로 감사한다 —
|
|
361
|
+
나머지 8건처럼 사람이 추적 소스를 동시에 고친 경우는 이 면제가 가리지
|
|
362
|
+
않는다. git 이 없거나 레포가 아니면 빈 집합을 돌려 종전대로 전부 감사한다.
|
|
363
|
+
"""
|
|
364
|
+
result = subprocess.run(
|
|
365
|
+
["git", "-C", str(root), "ls-files", "-z", "--others", "--ignored",
|
|
366
|
+
"--exclude-standard", "--directory"],
|
|
367
|
+
capture_output=True,
|
|
368
|
+
text=True,
|
|
369
|
+
check=False,
|
|
370
|
+
)
|
|
371
|
+
if result.returncode != 0:
|
|
372
|
+
return frozenset()
|
|
373
|
+
# `--directory` 는 통째로 무시되는 디렉터리를 `name/` 한 줄로 접는다.
|
|
374
|
+
# `_excluded` 는 접두사로 대조하므로 그 꼬리 슬래시를 떼어 준다.
|
|
375
|
+
return frozenset(
|
|
376
|
+
entry.rstrip("/") for entry in result.stdout.split("\0") if entry
|
|
377
|
+
)
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
def _tracked_paths(root: Path) -> frozenset[str] | None:
|
|
381
|
+
"""이 레포가 추적 중인 파일. git 이 없거나 레포가 아니면 ``None``.
|
|
382
|
+
|
|
383
|
+
`source-readonly` 가 묻는 것은 워커가 **소스** 를 바꿨는가이다. 추적 중인
|
|
384
|
+
파일의 변경이 그 답이다. 추적되지 않는 새 파일은 소스의 변경이 아니라
|
|
385
|
+
명령이 남긴 산출물이고, 그 둘을 한 등급으로 묶으면 규칙을 만족시킬 수 있는
|
|
386
|
+
실물 조건이 없어진다 — 프로필은 검증자에게 승인 계획의 Tier 1 명령을 독립
|
|
387
|
+
재실행하라고 요구하는데, 실제 프로젝트의 그 명령들은 빌드 산출물과 평가
|
|
388
|
+
리포트를 쓴다. 실측(2026-08-26, `fontsninja-nlpvibe` final-verification
|
|
389
|
+
run 005): 계획대로 게이트를 돌린 검증자 셋이 전부
|
|
390
|
+
`readonly source changed [eval/reports/clip-baseline.{html,json}]` 로
|
|
391
|
+
버려졌다. 두 파일은 추적되지 않는 생성 산출물이고, 계획이 승인 시점에
|
|
392
|
+
적어 둔 명령이 만든 것이다.
|
|
393
|
+
|
|
394
|
+
``None`` 은 "판정할 수 없음" 이다 — 그때는 좁히지 않고 종전대로 전부
|
|
395
|
+
소스 변경으로 본다. 추적 여부를 모르면서 면제하면 진짜 소스 변경을 놓친다.
|
|
396
|
+
"""
|
|
397
|
+
result = subprocess.run(
|
|
398
|
+
["git", "-C", str(root), "ls-files", "-z"],
|
|
399
|
+
capture_output=True,
|
|
400
|
+
text=True,
|
|
401
|
+
check=False,
|
|
402
|
+
)
|
|
403
|
+
if result.returncode != 0:
|
|
404
|
+
return None
|
|
405
|
+
return frozenset(entry for entry in result.stdout.split("\0") if entry)
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
def _split_tracked(root: Path, changes: set[str]) -> tuple[set[str], set[str]]:
|
|
409
|
+
"""``(추적 중인 소스 변경, 추적되지 않는 산출물)``."""
|
|
410
|
+
tracked = _tracked_paths(root)
|
|
411
|
+
if tracked is None:
|
|
412
|
+
return set(changes), set()
|
|
413
|
+
untracked = {path for path in changes if path not in tracked}
|
|
414
|
+
return changes - untracked, untracked
|
|
333
415
|
|
|
334
416
|
|
|
335
417
|
def _generated_paths(policies: Sequence[WritePolicy]) -> frozenset[str]:
|
|
@@ -119,3 +119,20 @@ def translation_sidecar_path(data_path: Path, lang: str) -> Path:
|
|
|
119
119
|
overwrite it.
|
|
120
120
|
"""
|
|
121
121
|
return data_path.with_name(f"{_stem(data_path)}.i18n.{lang}.json")
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def sidecar_source_rel(path: Path) -> str:
|
|
125
|
+
"""사이드카 프론트매터가 `source-report` / `source-data` 에 적는 경로.
|
|
126
|
+
|
|
127
|
+
기준점은 **태스크 루트**다 (`runs/<task-type>/reports/...`). 사이드카를
|
|
128
|
+
읽는 쪽은 리포트를 태스크 루트에서 되짚으므로
|
|
129
|
+
(`implementation_direction._relative_source`), 다른 기준으로 적힌 값은
|
|
130
|
+
같은 파일을 가리켜도 대조에 실패한다.
|
|
131
|
+
|
|
132
|
+
HTML 열람본을 렌더할 때 넘기는 `--source-report` 와 CLI 가 직접 쓰는
|
|
133
|
+
사이드카가 이 함수 하나를 공유한다. 둘이 각자 계산하던 동안 HTML Export 는
|
|
134
|
+
프로젝트 루트 기준을 적었고, 그 사이드카는 방향 선택 게이트를 통과할 수
|
|
135
|
+
없었다.
|
|
136
|
+
"""
|
|
137
|
+
indices = [index for index, part in enumerate(path.parts) if part == "runs"]
|
|
138
|
+
return Path(*path.parts[indices[-1]:]).as_posix() if indices else path.name
|
|
@@ -4,7 +4,8 @@ This is a deliberately narrow JSON Schema implementation that supports
|
|
|
4
4
|
exactly the keywords used by ``schemas/final-report-v2.0.schema.json``:
|
|
5
5
|
|
|
6
6
|
type, required, properties, additionalProperties, enum, const, pattern,
|
|
7
|
-
minLength, minItems, maxItems, uniqueItems, items, minimum, maximum,
|
|
7
|
+
minLength, minItems, maxItems, uniqueItems, prefixItems, items, minimum, maximum,
|
|
8
|
+
boolean schemas, $ref ($defs),
|
|
8
9
|
oneOf, allOf, if/then/else, contains, minContains, maxContains, not
|
|
9
10
|
|
|
10
11
|
We do NOT depend on the ``jsonschema`` PyPI package because its
|
|
@@ -102,12 +103,32 @@ def _resolve_ref(ref: str, root: dict) -> dict:
|
|
|
102
103
|
return node
|
|
103
104
|
|
|
104
105
|
|
|
106
|
+
_BRANCH_ROOT_PREFIX = f"{_format_path(())}: "
|
|
107
|
+
"""분기 probe 의 경로는 분기 루트 기준이라 `<root>:` 가 매 줄 반복된다.
|
|
108
|
+
바깥 오류가 이미 실제 경로를 싣고 있으므로 안쪽에서는 뗀다."""
|
|
109
|
+
|
|
110
|
+
_BRANCH_ERROR_LIMIT = 8
|
|
111
|
+
"""분기 하나당 실을 오류 수. 나머지는 개수만 적는다 — 열세 개 짜리 목록도
|
|
112
|
+
앞의 여덟이면 어느 블록을 안 썼는지 판정된다."""
|
|
113
|
+
|
|
114
|
+
|
|
105
115
|
class _Validator:
|
|
106
116
|
def __init__(self, root_schema: dict):
|
|
107
117
|
self.root = root_schema
|
|
108
118
|
self.errors: list[str] = []
|
|
109
119
|
|
|
110
|
-
def validate(
|
|
120
|
+
def validate(
|
|
121
|
+
self,
|
|
122
|
+
instance: Any,
|
|
123
|
+
schema: dict | bool,
|
|
124
|
+
path: tuple[str | int, ...],
|
|
125
|
+
) -> None:
|
|
126
|
+
if schema is True:
|
|
127
|
+
return
|
|
128
|
+
if schema is False:
|
|
129
|
+
self._err(path, "value is not allowed by the false schema")
|
|
130
|
+
return
|
|
131
|
+
|
|
111
132
|
# Handle $ref first — everything else is composed under the
|
|
112
133
|
# resolved schema.
|
|
113
134
|
if "$ref" in schema:
|
|
@@ -196,9 +217,14 @@ class _Validator:
|
|
|
196
217
|
for previous in instance[:index]
|
|
197
218
|
):
|
|
198
219
|
self._err(path, "array items are not unique")
|
|
220
|
+
prefix_items = schema.get("prefixItems") or []
|
|
221
|
+
for i, item_schema in enumerate(prefix_items[: len(instance)]):
|
|
222
|
+
self.validate(instance[i], item_schema, path + (i,))
|
|
223
|
+
|
|
199
224
|
items = schema.get("items")
|
|
200
225
|
if items is not None:
|
|
201
|
-
|
|
226
|
+
remaining_items = instance[len(prefix_items) :]
|
|
227
|
+
for i, value in enumerate(remaining_items, start=len(prefix_items)):
|
|
202
228
|
self.validate(value, items, path + (i,))
|
|
203
229
|
contains = schema.get("contains")
|
|
204
230
|
if contains is not None:
|
|
@@ -229,30 +255,78 @@ class _Validator:
|
|
|
229
255
|
if else_schema is not None:
|
|
230
256
|
self.validate(instance, else_schema, path)
|
|
231
257
|
|
|
232
|
-
def _validate_one_of(
|
|
233
|
-
|
|
234
|
-
|
|
258
|
+
def _validate_one_of(
|
|
259
|
+
self,
|
|
260
|
+
instance: Any,
|
|
261
|
+
branches: list[dict | bool],
|
|
262
|
+
path: tuple[str | int, ...],
|
|
263
|
+
) -> None:
|
|
264
|
+
per_branch = [self._branch_errors(instance, branch) for branch in branches]
|
|
265
|
+
matches = sum(1 for errors in per_branch if not errors)
|
|
266
|
+
if matches == 1:
|
|
267
|
+
return
|
|
268
|
+
if matches == 0:
|
|
235
269
|
self._err(
|
|
236
270
|
path,
|
|
237
|
-
|
|
238
|
-
|
|
271
|
+
"oneOf matched no branch (expected exactly 1); "
|
|
272
|
+
+ self._branch_diagnosis(per_branch),
|
|
239
273
|
)
|
|
274
|
+
return
|
|
275
|
+
self._err(
|
|
276
|
+
path,
|
|
277
|
+
f"oneOf matched {matches} branches (expected exactly 1); "
|
|
278
|
+
f"branches: {[self._summarise(b) for b in branches]}",
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
@staticmethod
|
|
282
|
+
def _branch_diagnosis(per_branch: list[list[str]]) -> str:
|
|
283
|
+
"""왜 각 분기가 안 맞았는지. 분기 스키마를 인라인하지 않는다.
|
|
240
284
|
|
|
241
|
-
|
|
285
|
+
종전에는 `oneOf` 실패가 분기 스키마 두 개를 통째로 찍었다. 1,500자
|
|
286
|
+
한 줄이면서 **어느 필드가 빠졌는지는 말하지 않아서**, 읽는 사람이
|
|
287
|
+
narrative 의 키 목록을 뽑아 스키마의 `required` 와 손으로 대조해야
|
|
288
|
+
했다. 분기가 실제로 낸 오류가 그 대조의 답이므로 그것을 싣는다.
|
|
289
|
+
"""
|
|
290
|
+
parts: list[str] = []
|
|
291
|
+
for index, errors in enumerate(per_branch, start=1):
|
|
292
|
+
shown = [
|
|
293
|
+
error[len(_BRANCH_ROOT_PREFIX):]
|
|
294
|
+
if error.startswith(_BRANCH_ROOT_PREFIX)
|
|
295
|
+
else error
|
|
296
|
+
for error in errors[:_BRANCH_ERROR_LIMIT]
|
|
297
|
+
]
|
|
298
|
+
remainder = len(errors) - len(shown)
|
|
299
|
+
if remainder > 0:
|
|
300
|
+
shown.append(f"(+{remainder} more)")
|
|
301
|
+
parts.append(f"branch {index}: " + "; ".join(shown))
|
|
302
|
+
return " | ".join(parts)
|
|
303
|
+
|
|
304
|
+
def _branch_errors(self, instance: Any, schema: dict | bool) -> list[str]:
|
|
305
|
+
probe = _Validator(self.root)
|
|
306
|
+
probe.validate(instance, schema, ())
|
|
307
|
+
return probe.errors
|
|
308
|
+
|
|
309
|
+
def _validate_not(
|
|
310
|
+
self,
|
|
311
|
+
instance: Any,
|
|
312
|
+
sub_schema: dict | bool,
|
|
313
|
+
path: tuple[str | int, ...],
|
|
314
|
+
) -> None:
|
|
242
315
|
if self._matches(instance, sub_schema):
|
|
243
316
|
self._err(path, f"value must NOT match {self._summarise(sub_schema)}")
|
|
244
317
|
|
|
245
|
-
def _matches(self, instance: Any, schema: dict) -> bool:
|
|
246
|
-
"""Cheap 'does this validate' probe used by if/
|
|
318
|
+
def _matches(self, instance: Any, schema: dict | bool) -> bool:
|
|
319
|
+
"""Cheap 'does this validate' probe used by if/contains.
|
|
247
320
|
Returns True iff the sub-schema produces zero errors. Does not
|
|
248
|
-
mutate ``self.errors``.
|
|
321
|
+
mutate ``self.errors``. `oneOf` keeps the errors themselves
|
|
322
|
+
(`_branch_errors`) because it has to say why every branch failed.
|
|
249
323
|
"""
|
|
250
|
-
|
|
251
|
-
probe.validate(instance, schema, ())
|
|
252
|
-
return not probe.errors
|
|
324
|
+
return not self._branch_errors(instance, schema)
|
|
253
325
|
|
|
254
326
|
@staticmethod
|
|
255
|
-
def _summarise(schema: dict) -> str:
|
|
327
|
+
def _summarise(schema: dict | bool) -> str:
|
|
328
|
+
if isinstance(schema, bool):
|
|
329
|
+
return str(schema).lower()
|
|
256
330
|
keys = sorted(k for k in schema if k not in ("description", "$comment"))
|
|
257
331
|
return "{" + ", ".join(f"{k}={schema[k]!r}" for k in keys[:3]) + "}"
|
|
258
332
|
|
|
@@ -273,12 +273,32 @@ def _items_as_json(items: List[StaleItem]) -> list:
|
|
|
273
273
|
"suggested_commit": i.suggested_commit} for i in items]
|
|
274
274
|
|
|
275
275
|
|
|
276
|
+
_CLI_EPILOG = r"""기본은 검사:
|
|
277
|
+
stale 항목을 JSON 으로 출력하고, confirm 항목이 남으면 exit 2.
|
|
278
|
+
patch-id 로 내용 동일성이 증명되는 항목(auto)은 --apply 로 일괄 보정되고,
|
|
279
|
+
내용이 바뀐 항목(confirm)은 --stage/--use-ref 로만 보정된다.
|
|
280
|
+
|
|
281
|
+
Usage:
|
|
282
|
+
okstra git-reconcile --plan-run-root <dir> --project-id <id> \
|
|
283
|
+
--task-group <g> --task-id <t> --work-category <c> \
|
|
284
|
+
[--apply] [--stage <N> --use-ref <ref>] [--reset-anchor <ref>] [--json]
|
|
285
|
+
|
|
286
|
+
Exit codes:
|
|
287
|
+
0 stale 없음 또는 보정 완료
|
|
288
|
+
2 confirm 항목 잔존 (check: 확인 필요 / apply: 일부 미보정)
|
|
289
|
+
1 error (resolve 실패 등)
|
|
290
|
+
"""
|
|
291
|
+
_CLI_DESCRIPTION = "Okstra \ubc16 git \ud788\uc2a4\ud1a0\ub9ac \ubcc0\uacbd \ud6c4 \uae30\ub85d \ud654\ud574."
|
|
292
|
+
|
|
293
|
+
|
|
276
294
|
def _parse_args(argv: Optional[list]):
|
|
277
295
|
import argparse
|
|
278
296
|
|
|
279
297
|
p = argparse.ArgumentParser(
|
|
280
|
-
|
|
281
|
-
epilog=
|
|
298
|
+
description=_CLI_DESCRIPTION,
|
|
299
|
+
epilog=_CLI_EPILOG,
|
|
300
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
301
|
+
prog="okstra git-reconcile")
|
|
282
302
|
p.add_argument("--project-root", default=".")
|
|
283
303
|
p.add_argument("--plan-run-root", required=True,
|
|
284
304
|
help="consumers.jsonl 이 있는 runs/implementation-planning/ 경로")
|
|
@@ -422,10 +422,33 @@ def _parse_stages_csv(raw: str) -> List[int]:
|
|
|
422
422
|
return out
|
|
423
423
|
|
|
424
424
|
|
|
425
|
+
_CLI_EPILOG = r"""JSON 출력.
|
|
426
|
+
|
|
427
|
+
Usage:
|
|
428
|
+
okstra handoff eligible --plan-run-root <dir> --approved-plan <md>
|
|
429
|
+
okstra handoff assemble --plan-run-root <dir> --approved-plan <md> \
|
|
430
|
+
--project-root <dir> --project-id <id> --task-group <g> --task-id <t> \
|
|
431
|
+
--work-category <c> --stages 2,3 --base <branch>
|
|
432
|
+
okstra handoff record-verified --plan-run-root <dir> --stage <N> \
|
|
433
|
+
--report-path <md> --data-json <json>
|
|
434
|
+
okstra handoff record-pr --plan-run-root <dir> --stages 2,3 \
|
|
435
|
+
--branch <b> --url <u>
|
|
436
|
+
okstra handoff local-checkout --project-root <dir> --project-id <id> \
|
|
437
|
+
--task-group <g> --task-id <t> [--stage <N>]
|
|
438
|
+
|
|
439
|
+
Exit codes: 0 ok / 1 자격·전제 위반 / 2 stage 간 merge 충돌(conflicts 동봉)
|
|
440
|
+
"""
|
|
441
|
+
_CLI_DESCRIPTION = "Release-handoff stage-group \ubcf4\uc870 (\uc790\uaca9/\uc218\uc9d1/\uae30\ub85d)."
|
|
442
|
+
|
|
443
|
+
|
|
425
444
|
def main(argv: Optional[list] = None) -> int:
|
|
426
445
|
import argparse
|
|
427
446
|
|
|
428
|
-
p = argparse.ArgumentParser(
|
|
447
|
+
p = argparse.ArgumentParser(
|
|
448
|
+
description=_CLI_DESCRIPTION,
|
|
449
|
+
epilog=_CLI_EPILOG,
|
|
450
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
451
|
+
prog="okstra handoff")
|
|
429
452
|
sub = p.add_subparsers(dest="cmd", required=True)
|
|
430
453
|
|
|
431
454
|
def common(sp, *, plan=True):
|
|
@@ -5,7 +5,9 @@ import hashlib as _hashlib
|
|
|
5
5
|
import re as _re
|
|
6
6
|
from typing import Dict
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
# leaf 규칙 모듈에서 바로 가져온다. state 를 거치면 okstra_ctl 패키지 __init__ 이
|
|
9
|
+
# 상태 접근자 전체를 끌어와 okstra_project ↔ okstra_ctl 순환이 된다.
|
|
10
|
+
from okstra_project.slug import slugify as _slugify
|
|
9
11
|
|
|
10
12
|
# docker compose project name 길이 상한. compose 는 [a-z0-9][a-z0-9_-]* 만 허용한다.
|
|
11
13
|
CONTAINER_PROJECT_NAME_MAX = 60
|
|
@@ -73,28 +75,16 @@ def _escape_segment_for_join(segment: str) -> str:
|
|
|
73
75
|
return segment.replace("-", "--")
|
|
74
76
|
|
|
75
77
|
|
|
76
|
-
def run_id_to_session_name(run_id: str) -> str:
|
|
77
|
-
"""tmux 세션명으로 변환한다. 'okstra-' 접두사 + 세그먼트 escape 후 `-` 조인.
|
|
78
|
-
예: 'a/b-c/r01' → 'okstra-a-b--c-r01'. 세그먼트 내부 `-` 는 `--` 로
|
|
79
|
-
이스케이프되어, 슬래시 위치가 다른 두 runId 가 동일 세션명으로
|
|
80
|
-
충돌하지 않는다. 세그먼트는 fs-safe 슬러그로 강제 정규화해
|
|
81
|
-
`..` 같은 traversal 토큰이 세션명에 그대로 노출되지 않게 한다.
|
|
82
|
-
"""
|
|
83
|
-
parts = [_escape_segment_for_join(_safe_fs_segment(seg))
|
|
84
|
-
for seg in run_id.split("/")]
|
|
85
|
-
return "okstra-" + "-".join(parts)
|
|
86
|
-
|
|
87
|
-
|
|
88
78
|
def slugify_task_segment(value: str) -> str:
|
|
89
79
|
"""Match okstra.sh slugify_value for task directory segments."""
|
|
90
80
|
return _re.sub(r"[^a-z0-9]+", "-", value.lower()).strip("-")
|
|
91
81
|
|
|
92
82
|
|
|
93
|
-
def
|
|
94
|
-
"""container
|
|
83
|
+
def build_container_run_trace(project_id: str, group: str, task_id: str) -> str:
|
|
84
|
+
"""container 배포의 `okstra.run-trace` 라벨값을 합친다 → 'okstra-container-<slug>'.
|
|
95
85
|
|
|
96
86
|
세그먼트는 task_dir 가 쓰는 slugify_task_segment 와 동일 규칙으로 정규화해
|
|
97
|
-
|
|
87
|
+
라벨 slug 가 프로젝트 디스크 경로 slug 와 분기하지 않게 한다.
|
|
98
88
|
"""
|
|
99
89
|
seg = slugify_task_segment
|
|
100
90
|
return f"okstra-container-{seg(project_id)}-{seg(group)}-{seg(task_id)}"
|