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
|
@@ -28,6 +28,7 @@ import subprocess
|
|
|
28
28
|
from dataclasses import asdict, dataclass, field
|
|
29
29
|
from datetime import datetime
|
|
30
30
|
from pathlib import Path
|
|
31
|
+
from collections.abc import Mapping
|
|
31
32
|
from typing import Any, Callable, Optional
|
|
32
33
|
|
|
33
34
|
from okstra_ctl.application.advance_wizard import plan_prompt
|
|
@@ -96,6 +97,7 @@ from okstra_ctl.clarification_items import (
|
|
|
96
97
|
from okstra_ctl.convergence_store import write_json_atomic
|
|
97
98
|
from okstra_ctl.json_boundary import JsonBoundaryError, load_owned_object
|
|
98
99
|
from okstra_ctl.incremental_scope import (
|
|
100
|
+
CARRY_ALL_SCOPE,
|
|
99
101
|
parse_stage_graph,
|
|
100
102
|
preview_link_availability_for_report,
|
|
101
103
|
)
|
|
@@ -242,6 +244,8 @@ _RECOMMENDATION_CAP = 3
|
|
|
242
244
|
# Pick-vs-free-text tokens shared by suggestion-aware prompts.
|
|
243
245
|
PICK_USE_SUGGESTED = "__use_suggested__"
|
|
244
246
|
PICK_TYPE_CUSTOM = "__free_input__"
|
|
247
|
+
# 취소지 탈출구가 아니다 — 자유 입력 뒤에 올 수 있는 유일한 값.
|
|
248
|
+
_ABORT_OPTION = "__abort__"
|
|
245
249
|
# workers_override 에서 "옵션 워커를 추가하지 않음" 을 뜻하는 sentinel.
|
|
246
250
|
_DEFAULT_ROSTER_TOKEN = "__default_roster__"
|
|
247
251
|
_RECENT_PREFIX = "__recent:"
|
|
@@ -590,6 +594,34 @@ class Prompt:
|
|
|
590
594
|
# only meaningful when kind == "pick_group": one entry per AskUserQuestion tab
|
|
591
595
|
questions: list["Prompt"] = field(default_factory=list)
|
|
592
596
|
|
|
597
|
+
def __post_init__(self) -> None:
|
|
598
|
+
"""자유 입력 선택지는 언제나 마지막이다.
|
|
599
|
+
|
|
600
|
+
목록이 자유 입력으로 열리면 질문을 통째로 사용자에게 되돌려준 것이다 —
|
|
601
|
+
앞선 선택지들이 이 run 이 이미 알아낸 것이고, 자유 입력은 그것들이
|
|
602
|
+
전부 맞지 않을 때의 탈출구다. 실측: 재검증 범위 질문의 `unlinked`
|
|
603
|
+
분기가 `직접 입력` 을 첫 선택지로 내놓았고, 리드가 preamble 에 해당
|
|
604
|
+
stage 번호를 이미 적어 놓은 화면에서도 사용자는 그 번호를 다시
|
|
605
|
+
타이핑해야 했다.
|
|
606
|
+
|
|
607
|
+
중단(abort)만 그 뒤에 올 수 있다. 중단은 탈출구가 아니라 취소이고,
|
|
608
|
+
`brief_carry` 가 실제로 그 모양이다 — 전환(추천) / 직접 입력 / 중단.
|
|
609
|
+
|
|
610
|
+
배치 지점이 12곳이라 관례로는 지켜지지 않는다. 값을 만드는 자리에서
|
|
611
|
+
막는다.
|
|
612
|
+
"""
|
|
613
|
+
values = [option.value for option in self.options]
|
|
614
|
+
if PICK_TYPE_CUSTOM not in values:
|
|
615
|
+
return
|
|
616
|
+
after = values[values.index(PICK_TYPE_CUSTOM) + 1:]
|
|
617
|
+
offenders = [value for value in after if value != _ABORT_OPTION]
|
|
618
|
+
if offenders:
|
|
619
|
+
raise WizardError(
|
|
620
|
+
f"wizard step {self.step!r}: the free-input option "
|
|
621
|
+
f"({PICK_TYPE_CUSTOM!r}) must come after every real choice — "
|
|
622
|
+
f"only {_ABORT_OPTION!r} may follow it, but {offenders} do"
|
|
623
|
+
)
|
|
624
|
+
|
|
593
625
|
@property
|
|
594
626
|
def id(self) -> str:
|
|
595
627
|
"""Stable public identifier shared by static and role-instance prompts."""
|
|
@@ -649,8 +681,11 @@ def _require_file(path_str: str, project_root: Path, label: str) -> Path:
|
|
|
649
681
|
return p
|
|
650
682
|
|
|
651
683
|
|
|
684
|
+
# 후보는 리포트 **기록**으로 찾는다. 전체 열람본(`.md`)은 `2bcd575` 이후 완료
|
|
685
|
+
# 산출물이 아니라 요청 시 렌더라, `.md` 를 글롭하는 동안 이 목록은 항상 비었고
|
|
686
|
+
# 방향 선택 단계 자체가 뜨지 않았다 — 비교가 끝나도 계획으로 갈 길이 없었다.
|
|
652
687
|
_SELECTION_REPORT_RE = re.compile(
|
|
653
|
-
r"^final-report-implementation-option-selection-(?P<seq>\d{3,})\.
|
|
688
|
+
r"^final-report-implementation-option-selection-(?P<seq>\d{3,})\.data\.json$"
|
|
654
689
|
)
|
|
655
690
|
|
|
656
691
|
|
|
@@ -667,7 +702,9 @@ def _selected_direction_candidates(state: WizardState) -> list[str]:
|
|
|
667
702
|
/ "reports"
|
|
668
703
|
)
|
|
669
704
|
candidates: list[tuple[int, Path]] = []
|
|
670
|
-
for report in reports.glob(
|
|
705
|
+
for report in reports.glob(
|
|
706
|
+
"final-report-implementation-option-selection-*.data.json"
|
|
707
|
+
):
|
|
671
708
|
match = _SELECTION_REPORT_RE.fullmatch(report.name)
|
|
672
709
|
if match is None:
|
|
673
710
|
continue
|
|
@@ -729,22 +766,55 @@ def _build_selected_direction_pick(state: WizardState) -> Prompt:
|
|
|
729
766
|
step=S_SELECTED_DIRECTION_PICK,
|
|
730
767
|
kind="pick",
|
|
731
768
|
label=t["label"],
|
|
732
|
-
options=[
|
|
769
|
+
options=[
|
|
770
|
+
_opt(path, _selection_option_label(state, path, t))
|
|
771
|
+
for path in candidates
|
|
772
|
+
],
|
|
733
773
|
echo_template=t["echo_template"],
|
|
734
774
|
)
|
|
735
775
|
|
|
736
776
|
|
|
777
|
+
def _selection_option_label(state: WizardState, rel_path: str, t: dict) -> str:
|
|
778
|
+
"""후보 경로에 그 리포트가 실제로 무엇을 담았는지 붙인다.
|
|
779
|
+
|
|
780
|
+
목록은 최신순이라 후보가 0건인 최근 재실행이 맨 위에 온다. 경로만 보이면
|
|
781
|
+
사용자가 그것을 고르고, 방향 없는 리포트로 계획을 열려다 막힌다.
|
|
782
|
+
"""
|
|
783
|
+
record = Path(state.project_root) / rel_path
|
|
784
|
+
try:
|
|
785
|
+
data = load_owned_object(record, artifact="implementation option data")
|
|
786
|
+
except (OSError, JsonBoundaryError):
|
|
787
|
+
return rel_path
|
|
788
|
+
selection = data.get("implementationOptionSelection")
|
|
789
|
+
if not isinstance(selection, Mapping):
|
|
790
|
+
return rel_path
|
|
791
|
+
rows = selection.get("rankedOptions")
|
|
792
|
+
ids = [
|
|
793
|
+
str(row.get("id"))
|
|
794
|
+
for row in rows
|
|
795
|
+
if isinstance(row, Mapping) and row.get("id")
|
|
796
|
+
] if isinstance(rows, list) else []
|
|
797
|
+
if not ids:
|
|
798
|
+
return t["labels"]["no_options"].format(path=rel_path)
|
|
799
|
+
return t["labels"]["options"].format(path=rel_path, ids=", ".join(ids))
|
|
800
|
+
|
|
801
|
+
|
|
737
802
|
def _submit_selected_direction_pick(
|
|
738
803
|
state: WizardState, value: str
|
|
739
804
|
) -> Optional[str]:
|
|
805
|
+
t = _p(state.workspace_root, S_SELECTED_DIRECTION_PICK)
|
|
740
806
|
candidates = _selected_direction_candidates(state)
|
|
741
807
|
if value not in candidates:
|
|
742
|
-
raise WizardError(
|
|
743
|
-
_p(state.workspace_root, S_SELECTED_DIRECTION_PICK)["errors"][
|
|
744
|
-
"unknown"
|
|
745
|
-
].format(value=value)
|
|
746
|
-
)
|
|
808
|
+
raise WizardError(t["errors"]["unknown"].format(value=value))
|
|
747
809
|
state.selected_direction_path = value
|
|
810
|
+
# 두 입력은 상호 배타다 — 방향이 정해진 순간 이 런은 새 계획이고,
|
|
811
|
+
# clarification 자리에 남은 값은 render-bundle 이 거절할 이유일 뿐이다.
|
|
812
|
+
# 거절을 여기서 앞당기는 대신 값을 비우고, 무엇이 비워졌는지 echo 로
|
|
813
|
+
# 알린다. 사용자 답변은 손실되지 않는다: 그 사이드카는
|
|
814
|
+
# `--selected-direction` 경로로 계획 런에 첨부된다(`run.py` 참조).
|
|
815
|
+
if state.clarification_response_path:
|
|
816
|
+
state.clarification_response_path = ""
|
|
817
|
+
return t["echo_variants"]["cleared_clarification"].format(value=value)
|
|
748
818
|
return f"selected-direction: {value}"
|
|
749
819
|
|
|
750
820
|
|
|
@@ -1191,8 +1261,6 @@ def _convert_v1_provider_selections(
|
|
|
1191
1261
|
for requirement in profile.roles
|
|
1192
1262
|
if not requirement.dynamic and requirement.min_count > 0
|
|
1193
1263
|
}
|
|
1194
|
-
if state.host_entry_mode == "spawn-process":
|
|
1195
|
-
static_roles.add("leader")
|
|
1196
1264
|
for role, provider, raw_model in provider_rows:
|
|
1197
1265
|
if role not in static_roles or not isinstance(provider, str) or not provider:
|
|
1198
1266
|
continue
|
|
@@ -1231,6 +1299,7 @@ def _wizard_state_from_json(payload: dict[str, Any]) -> WizardState:
|
|
|
1231
1299
|
if version != 2:
|
|
1232
1300
|
state.execution_identity_version = 2
|
|
1233
1301
|
_convert_v1_provider_selections(state, payload)
|
|
1302
|
+
_discard_implicit_leader_selection(state)
|
|
1234
1303
|
return state
|
|
1235
1304
|
|
|
1236
1305
|
|
|
@@ -1274,6 +1343,24 @@ def _is_role_selection_step(step_id: str) -> bool:
|
|
|
1274
1343
|
return step_id.startswith(("role-count:", "role-model:", "role-add:"))
|
|
1275
1344
|
|
|
1276
1345
|
|
|
1346
|
+
def _discard_implicit_leader_selection(state: WizardState) -> None:
|
|
1347
|
+
"""호스트에 고정된 리더의 구형 선택값을 상태에서 제거한다."""
|
|
1348
|
+
state.role_models.pop("leader", None)
|
|
1349
|
+
leader_steps = {
|
|
1350
|
+
step_id
|
|
1351
|
+
for step_id in state.role_selection_order
|
|
1352
|
+
if step_id.startswith("role-model:leader:")
|
|
1353
|
+
}
|
|
1354
|
+
state.role_selection_order = [
|
|
1355
|
+
step_id for step_id in state.role_selection_order
|
|
1356
|
+
if step_id not in leader_steps
|
|
1357
|
+
]
|
|
1358
|
+
state.answered = [
|
|
1359
|
+
step_id for step_id in state.answered
|
|
1360
|
+
if step_id not in leader_steps
|
|
1361
|
+
]
|
|
1362
|
+
|
|
1363
|
+
|
|
1277
1364
|
def _selected_role_count(
|
|
1278
1365
|
state: WizardState,
|
|
1279
1366
|
requirement: RoleRequirement,
|
|
@@ -1292,16 +1379,12 @@ def _selectable_static_requirements(
|
|
|
1292
1379
|
state: WizardState,
|
|
1293
1380
|
profile: RoleProfile,
|
|
1294
1381
|
) -> tuple[RoleRequirement, ...]:
|
|
1295
|
-
|
|
1296
|
-
if state.host_entry_mode == "spawn-process":
|
|
1297
|
-
requirements.append(RoleRequirement("leader", 1, 1, 1, "lead"))
|
|
1298
|
-
requirements.extend(
|
|
1382
|
+
return tuple(
|
|
1299
1383
|
requirement
|
|
1300
1384
|
for requirement in profile.roles
|
|
1301
1385
|
if not requirement.dynamic
|
|
1302
1386
|
and _selected_role_count(state, requirement) > 0
|
|
1303
1387
|
)
|
|
1304
|
-
return tuple(requirements)
|
|
1305
1388
|
|
|
1306
1389
|
|
|
1307
1390
|
def _count_prompt(
|
|
@@ -1354,6 +1437,14 @@ def _role_add_prompt(
|
|
|
1354
1437
|
maximum=str(requirement.max_count),
|
|
1355
1438
|
)
|
|
1356
1439
|
suffix = prompt["options"].get("default_suffix", "")
|
|
1440
|
+
# 역할을 빼면 그 역할이 맡던 판정이 사라진다. 그 결과를 아는 역할만 0 옵션에
|
|
1441
|
+
# 경고를 단다 — 예: critic 이 없으면 분석자 동수를 가를 주체가 없다.
|
|
1442
|
+
skip_warnings = prompt["options"].get("skip_warnings", {})
|
|
1443
|
+
skip_warning = (
|
|
1444
|
+
skip_warnings.get(requirement.role, "")
|
|
1445
|
+
if isinstance(skip_warnings, dict)
|
|
1446
|
+
else ""
|
|
1447
|
+
)
|
|
1357
1448
|
|
|
1358
1449
|
def _default_suffix(count: int) -> str:
|
|
1359
1450
|
return suffix if count == requirement.recommended_count else ""
|
|
@@ -1361,7 +1452,10 @@ def _role_add_prompt(
|
|
|
1361
1452
|
options = [
|
|
1362
1453
|
_opt(
|
|
1363
1454
|
"0",
|
|
1364
|
-
prompt["options"]["skip"].format(
|
|
1455
|
+
prompt["options"]["skip"].format(
|
|
1456
|
+
default_suffix=_default_suffix(0),
|
|
1457
|
+
skip_warning=skip_warning,
|
|
1458
|
+
),
|
|
1365
1459
|
),
|
|
1366
1460
|
]
|
|
1367
1461
|
for count in range(1, requirement.max_count + 1):
|
|
@@ -1984,10 +2078,12 @@ def _p(workspace_root: str, step_id: str, **vars: str) -> dict:
|
|
|
1984
2078
|
label_template = raw.get("label", "")
|
|
1985
2079
|
fv_label_template = raw.get("label_final_verification", "")
|
|
1986
2080
|
unlinked_template = raw.get("label_unlinked", "")
|
|
2081
|
+
no_answers_template = raw.get("label_no_answers", "")
|
|
1987
2082
|
try:
|
|
1988
2083
|
label = label_template.format(**vars)
|
|
1989
2084
|
label_final_verification = fv_label_template.format(**vars)
|
|
1990
2085
|
label_unlinked = unlinked_template.format(**vars)
|
|
2086
|
+
label_no_answers = no_answers_template.format(**vars)
|
|
1991
2087
|
except KeyError as exc:
|
|
1992
2088
|
missing = exc.args[0] if exc.args else "<unknown>"
|
|
1993
2089
|
raise WizardError(
|
|
@@ -1997,6 +2093,7 @@ def _p(workspace_root: str, step_id: str, **vars: str) -> dict:
|
|
|
1997
2093
|
"label": label,
|
|
1998
2094
|
"label_final_verification": label_final_verification,
|
|
1999
2095
|
"label_unlinked": label_unlinked,
|
|
2096
|
+
"label_no_answers": label_no_answers,
|
|
2000
2097
|
"echo_template": raw.get("echo_template", ""),
|
|
2001
2098
|
"options": raw.get("options", {}),
|
|
2002
2099
|
"options_final_verification": raw.get("options_final_verification", {}),
|
|
@@ -2304,7 +2401,7 @@ def _submit_task_pick(state: WizardState, value: str) -> Optional[str]:
|
|
|
2304
2401
|
# task_type seeded from manifest's nextRecommendedPhase as the recommended default
|
|
2305
2402
|
root = find_task_root(Path(state.project_root), value)
|
|
2306
2403
|
manifest = (read_task_manifest(root) or {}) if root else {}
|
|
2307
|
-
state.task_type =
|
|
2404
|
+
state.task_type = _recommended_task_type(state, manifest)
|
|
2308
2405
|
return f"task: {value}"
|
|
2309
2406
|
|
|
2310
2407
|
|
|
@@ -2592,20 +2689,81 @@ def _submit_task_id_text(state: WizardState, value: str) -> Optional[str]:
|
|
|
2592
2689
|
return f"task-id: {state.task_id}"
|
|
2593
2690
|
|
|
2594
2691
|
|
|
2692
|
+
def _existing_task_manifest(state: WizardState) -> dict[str, Any]:
|
|
2693
|
+
"""현재 task-key 의 task-manifest. 없으면 {}."""
|
|
2694
|
+
if not (state.project_id and state.task_group and state.task_id):
|
|
2695
|
+
return {}
|
|
2696
|
+
key = f"{state.project_id}:{state.task_group}:{state.task_id}"
|
|
2697
|
+
root = find_task_root(Path(state.project_root), key)
|
|
2698
|
+
if root is None:
|
|
2699
|
+
return {}
|
|
2700
|
+
manifest = read_task_manifest(root) or {}
|
|
2701
|
+
return manifest if isinstance(manifest, dict) else {}
|
|
2702
|
+
|
|
2703
|
+
|
|
2595
2704
|
def _existing_task_workflow(state: WizardState) -> dict:
|
|
2596
2705
|
"""현재 task-key 가 이미 존재하면 그 manifest 의 workflow dict 를 반환한다.
|
|
2597
2706
|
|
|
2598
2707
|
picker 로 기존 task 를 고른 경우뿐 아니라, new-task 흐름으로 같은
|
|
2599
2708
|
task-group/task-id 를 다시 입력한 경우(=사실상 이어가기)에도 직전 phase
|
|
2600
2709
|
기반 추천이 끊기지 않게 하는 안전장치. 없으면 {}."""
|
|
2601
|
-
|
|
2710
|
+
workflow = _existing_task_manifest(state).get("workflow") or {}
|
|
2711
|
+
return workflow if isinstance(workflow, dict) else {}
|
|
2712
|
+
|
|
2713
|
+
|
|
2714
|
+
def _report_data_for_pointer(
|
|
2715
|
+
state: WizardState, manifest: Mapping[str, Any],
|
|
2716
|
+
) -> dict[str, Any]:
|
|
2717
|
+
rel = manifest.get("latestReportRecordPath")
|
|
2718
|
+
if not isinstance(rel, str) or not rel.strip():
|
|
2602
2719
|
return {}
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2720
|
+
path = Path(rel)
|
|
2721
|
+
if not path.is_absolute():
|
|
2722
|
+
path = Path(state.project_root) / rel
|
|
2723
|
+
try:
|
|
2724
|
+
data = load_owned_object(path, artifact="latest report record")
|
|
2725
|
+
except (JsonBoundaryError, OSError):
|
|
2606
2726
|
return {}
|
|
2607
|
-
|
|
2608
|
-
|
|
2727
|
+
return data if isinstance(data, dict) else {}
|
|
2728
|
+
|
|
2729
|
+
|
|
2730
|
+
def _recommended_task_type(
|
|
2731
|
+
state: WizardState, manifest: Mapping[str, Any],
|
|
2732
|
+
) -> str:
|
|
2733
|
+
"""매니페스트 포인터가 ready 면 그 phase, 끝난 계획의 낡은 pending 이면 리포트 투영."""
|
|
2734
|
+
filled = next_phase.autofill_task_type(manifest)
|
|
2735
|
+
if filled:
|
|
2736
|
+
return filled
|
|
2737
|
+
workflow = manifest.get("workflow")
|
|
2738
|
+
if not isinstance(workflow, Mapping):
|
|
2739
|
+
return ""
|
|
2740
|
+
if (
|
|
2741
|
+
workflow.get("currentPhase") != "implementation-planning"
|
|
2742
|
+
or workflow.get("currentPhaseState") != "completed"
|
|
2743
|
+
):
|
|
2744
|
+
return ""
|
|
2745
|
+
pointer = next_phase.project(_report_data_for_pointer(state, manifest))
|
|
2746
|
+
if pointer["status"] != next_phase.STATUS_READY:
|
|
2747
|
+
return ""
|
|
2748
|
+
return pointer["phase"]
|
|
2749
|
+
|
|
2750
|
+
|
|
2751
|
+
def _implementation_needs_approval(
|
|
2752
|
+
state: WizardState,
|
|
2753
|
+
manifest: Mapping[str, Any],
|
|
2754
|
+
workflow: Mapping[str, Any],
|
|
2755
|
+
) -> bool:
|
|
2756
|
+
if workflow.get("awaitingApproval") is True:
|
|
2757
|
+
return True
|
|
2758
|
+
if (
|
|
2759
|
+
workflow.get("currentPhase") != "implementation-planning"
|
|
2760
|
+
or workflow.get("currentPhaseState") != "completed"
|
|
2761
|
+
):
|
|
2762
|
+
return False
|
|
2763
|
+
frontmatter = _report_data_for_pointer(state, manifest).get("frontmatter")
|
|
2764
|
+
if not isinstance(frontmatter, dict):
|
|
2765
|
+
return True
|
|
2766
|
+
return frontmatter.get("approved") is not True
|
|
2609
2767
|
|
|
2610
2768
|
|
|
2611
2769
|
def _phase_after(task_type: str) -> str:
|
|
@@ -2748,7 +2906,8 @@ def _build_task_type(state: WizardState) -> Prompt:
|
|
|
2748
2906
|
options.append(_opt(task_type, f"{task_type}{suffix}",
|
|
2749
2907
|
description_by_type[task_type]))
|
|
2750
2908
|
|
|
2751
|
-
|
|
2909
|
+
manifest = _existing_task_manifest(state)
|
|
2910
|
+
workflow = manifest.get("workflow") if isinstance(manifest.get("workflow"), dict) else {}
|
|
2752
2911
|
revision_requested = _latest_revision_requested_analysis_type(state)
|
|
2753
2912
|
# 포인터에서 phase 를 꺼내는 것은 `status` 가 `ready` 일 때뿐이다. prepare 는
|
|
2754
2913
|
# 실행이 끝나지 않은 run 의 포인터를 일부러 `pending` 으로 내려두는데
|
|
@@ -2760,15 +2919,18 @@ def _build_task_type(state: WizardState) -> Prompt:
|
|
|
2760
2919
|
# `ready` 가 아니면 추천은 비고, 아래 `currentPhase` 재실행 옵션이 남는다.
|
|
2761
2920
|
# 실패한 run 의 포인터가 `{"phase": "", "status": "blocked"}` 라는 점에서
|
|
2762
2921
|
# 그것이 맞는 제안이다 — 그 옵션은 포인터가 아니라 `currentPhase` 에서 온다.
|
|
2763
|
-
#
|
|
2764
|
-
#
|
|
2765
|
-
# 재실행은 답이 기록된 뒤에만 고르라고
|
|
2922
|
+
# 끝난 계획의 낡은 pending 은 최신 리포트에서 다시 투영한다. 승인 대기는
|
|
2923
|
+
# `awaitingApproval` 또는 미승인 계획 리포트로 접미사를 붙인다. 열린
|
|
2924
|
+
# C-NNN 때문에 blocked 면 재실행은 답이 기록된 뒤에만 고르라고 말한다.
|
|
2766
2925
|
recommended = (revision_requested or state.task_type
|
|
2767
|
-
or
|
|
2926
|
+
or _recommended_task_type(state, manifest))
|
|
2768
2927
|
if not recommended and not workflow:
|
|
2769
2928
|
recommended = TASK_TYPE_VALUES[0]
|
|
2770
2929
|
pointer = next_phase.promote(workflow.get("nextRecommendedPhase"))
|
|
2771
|
-
if
|
|
2930
|
+
if (
|
|
2931
|
+
recommended == "implementation"
|
|
2932
|
+
and _implementation_needs_approval(state, manifest, workflow)
|
|
2933
|
+
):
|
|
2772
2934
|
recommended_suffix = approve_suffix
|
|
2773
2935
|
if (
|
|
2774
2936
|
pointer["status"] == next_phase.STATUS_BLOCKED
|
|
@@ -2955,7 +3117,7 @@ def _submit_brief_path(state: WizardState, value: str) -> Optional[str]:
|
|
|
2955
3117
|
|
|
2956
3118
|
|
|
2957
3119
|
BRIEF_CARRY_SWITCH_ENTRY = "__switch_entry__"
|
|
2958
|
-
BRIEF_CARRY_ABORT =
|
|
3120
|
+
BRIEF_CARRY_ABORT = _ABORT_OPTION
|
|
2959
3121
|
|
|
2960
3122
|
|
|
2961
3123
|
def _build_brief_carry(state: WizardState) -> Prompt:
|
|
@@ -3431,19 +3593,25 @@ def _design_prep_items_by_id(state: WizardState) -> dict[str, dict[str, Any]]:
|
|
|
3431
3593
|
return {str(item["id"]): item for item in items}
|
|
3432
3594
|
|
|
3433
3595
|
|
|
3434
|
-
def
|
|
3435
|
-
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
|
|
3596
|
+
def _selected_impl_stage_numbers(state: WizardState) -> set[int]:
|
|
3597
|
+
numbers: set[int] = set()
|
|
3598
|
+
for token in (state.selected_stages or state.selected_stage or "").split(","):
|
|
3599
|
+
token = token.strip()
|
|
3600
|
+
if token.isdigit():
|
|
3601
|
+
numbers.add(int(token))
|
|
3602
|
+
return numbers
|
|
3603
|
+
|
|
3604
|
+
|
|
3605
|
+
def _blocking_unanswered_prep_ids(state: WizardState) -> list[str] | None:
|
|
3606
|
+
"""고른 스테이지에서 구현을 막는 unanswered blocked id. 로드 불가면 None."""
|
|
3439
3607
|
report_path = Path(state.approved_plan_path)
|
|
3440
3608
|
if not re.fullmatch(
|
|
3441
3609
|
r"final-report-implementation-planning-\d+\.data\.json",
|
|
3442
3610
|
report_path.name,
|
|
3443
3611
|
):
|
|
3444
|
-
return
|
|
3612
|
+
return None
|
|
3445
3613
|
if not report_path.is_file():
|
|
3446
|
-
return
|
|
3614
|
+
return None
|
|
3447
3615
|
try:
|
|
3448
3616
|
items = load_design_prep_items(report_path)
|
|
3449
3617
|
effective = resolve_design_prep(report_path)
|
|
@@ -3453,13 +3621,48 @@ def _ensure_design_prep_queue(state: WizardState) -> None:
|
|
|
3453
3621
|
str(item["id"]): item.get("decision")
|
|
3454
3622
|
for item in effective.effective_items
|
|
3455
3623
|
}
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3624
|
+
stages = _selected_impl_stage_numbers(state)
|
|
3625
|
+
# provisional 미응답은 구현을 막지 않는다. 작업 가정을 그대로 쓴다.
|
|
3626
|
+
return [
|
|
3627
|
+
str(item["id"])
|
|
3628
|
+
for item in items
|
|
3629
|
+
if item.get("status") == "blocked"
|
|
3459
3630
|
and decisions.get(str(item["id"])) == "unanswered"
|
|
3631
|
+
and (
|
|
3632
|
+
not stages
|
|
3633
|
+
or stages.intersection(
|
|
3634
|
+
stage for stage in (item.get("stageRefs") or [])
|
|
3635
|
+
if isinstance(stage, int)
|
|
3636
|
+
)
|
|
3637
|
+
)
|
|
3638
|
+
]
|
|
3639
|
+
|
|
3640
|
+
|
|
3641
|
+
def _prune_design_prep_session(state: WizardState, wanted: list[str]) -> None:
|
|
3642
|
+
"""이미 만든 큐에서 더 이상 막지 않는 id 를 뺀다. 새 id 는 넣지 않는다."""
|
|
3643
|
+
wanted_set = set(wanted)
|
|
3644
|
+
if state.design_prep_current and state.design_prep_current not in wanted_set:
|
|
3645
|
+
state.design_prep_current = ""
|
|
3646
|
+
state.design_prep_decision = ""
|
|
3647
|
+
state.design_prep_overrides_json = ""
|
|
3648
|
+
state.design_prep_notes = ""
|
|
3649
|
+
state.design_prep_queue = [
|
|
3650
|
+
item_id for item_id in state.design_prep_queue if item_id in wanted_set
|
|
3460
3651
|
]
|
|
3461
|
-
|
|
3462
|
-
|
|
3652
|
+
if not state.design_prep_current:
|
|
3653
|
+
_advance_design_prep_item(state)
|
|
3654
|
+
|
|
3655
|
+
|
|
3656
|
+
def _ensure_design_prep_queue(state: WizardState) -> None:
|
|
3657
|
+
wanted = _blocking_unanswered_prep_ids(state)
|
|
3658
|
+
if wanted is None:
|
|
3659
|
+
return
|
|
3660
|
+
if state.design_prep_current or state.design_prep_queue:
|
|
3661
|
+
_prune_design_prep_session(state, wanted)
|
|
3662
|
+
return
|
|
3663
|
+
if S_DESIGN_PREP_DECISION in state.answered:
|
|
3664
|
+
return
|
|
3665
|
+
state.design_prep_queue = list(wanted)
|
|
3463
3666
|
_advance_design_prep_item(state)
|
|
3464
3667
|
|
|
3465
3668
|
|
|
@@ -3579,6 +3782,8 @@ def _submit_design_prep_overrides(
|
|
|
3579
3782
|
|
|
3580
3783
|
|
|
3581
3784
|
def _design_prep_confirm_applies(state: WizardState) -> bool:
|
|
3785
|
+
if state.task_type == "implementation" and state.approved_plan_path:
|
|
3786
|
+
_ensure_design_prep_queue(state)
|
|
3582
3787
|
if state.design_prep_decision == "accept-draft":
|
|
3583
3788
|
return True
|
|
3584
3789
|
if state.design_prep_decision == "modify-draft":
|
|
@@ -3916,7 +4121,12 @@ def _suggest_latest_final_report(state: WizardState) -> str:
|
|
|
3916
4121
|
f"{seg}/reports/final-report-*.data.json",
|
|
3917
4122
|
Path(state.project_root),
|
|
3918
4123
|
)
|
|
3919
|
-
|
|
4124
|
+
# implementation-planning 의 `--clarification-response` 는 같은 phase 의
|
|
4125
|
+
# 계획서만 받는다(`run._validate_planning_entry_inputs`). 그 phase 에 아직
|
|
4126
|
+
# 리포트가 없으면 이 런은 새 계획이고, 답은 `--selected-direction` 으로
|
|
4127
|
+
# 들어간다 — 전체 phase 폴백은 여기서 직전 후보비교 리포트를 추천했고,
|
|
4128
|
+
# 그것을 고른 사용자는 두 상호 배타 입력을 동시에 갖게 됐다.
|
|
4129
|
+
if best is None and state.task_type != "implementation-planning":
|
|
3920
4130
|
best = _newest_contained_final_report(
|
|
3921
4131
|
runs_base,
|
|
3922
4132
|
task_root,
|
|
@@ -4148,16 +4358,58 @@ def _reverify_scope_preview(state: WizardState) -> Optional[dict]:
|
|
|
4148
4358
|
)
|
|
4149
4359
|
|
|
4150
4360
|
|
|
4151
|
-
|
|
4152
|
-
|
|
4361
|
+
SCOPE_SHAPE_LINKED = "linked"
|
|
4362
|
+
SCOPE_SHAPE_UNLINKED = "unlinked"
|
|
4363
|
+
SCOPE_SHAPE_NO_ANSWERS = "no-answers"
|
|
4364
|
+
|
|
4365
|
+
|
|
4366
|
+
def _reverify_scope_shape(state: WizardState) -> str:
|
|
4367
|
+
"""이 재실행에서 되짚기(back-trace)가 무엇을 내놓았는가.
|
|
4153
4368
|
|
|
4154
|
-
|
|
4155
|
-
|
|
4369
|
+
셋은 서로 다른 picker 를 요구한다 — `linked` 는 자동 판정이 실제로 좁힐 수
|
|
4370
|
+
있고, 나머지 둘은 자동이 낼 수 있는 답이 full 뿐이라 추천으로 내놓으면
|
|
4371
|
+
거짓말이 된다.
|
|
4156
4372
|
"""
|
|
4157
|
-
preview = _reverify_scope_preview(state)
|
|
4158
|
-
|
|
4159
|
-
|
|
4160
|
-
)
|
|
4373
|
+
preview = _reverify_scope_preview(state) or {}
|
|
4374
|
+
if preview.get("unlinkedIds"):
|
|
4375
|
+
return SCOPE_SHAPE_UNLINKED
|
|
4376
|
+
if preview.get("wouldForceFull"):
|
|
4377
|
+
return SCOPE_SHAPE_NO_ANSWERS
|
|
4378
|
+
return SCOPE_SHAPE_LINKED
|
|
4379
|
+
|
|
4380
|
+
|
|
4381
|
+
def _prior_stage_map_readable(state: WizardState) -> bool:
|
|
4382
|
+
"""직전 리포트의 Stage Map 을 읽을 수 있는가 — 번호를 검증할 수 있는가."""
|
|
4383
|
+
try:
|
|
4384
|
+
return bool(_prior_stage_numbers(state))
|
|
4385
|
+
except WizardError:
|
|
4386
|
+
return False
|
|
4387
|
+
|
|
4388
|
+
|
|
4389
|
+
def _reverify_scope_pick_required(state: WizardState) -> bool:
|
|
4390
|
+
"""직전 리포트를 읽을 수 있으면 묻는다.
|
|
4391
|
+
|
|
4392
|
+
종전에는 `preview_link_availability` 의 `wouldForceFull` 이 참이면 묻지
|
|
4393
|
+
않았다. 그 값은 답변된 id 가 0건일 때도 참인데, 그 경우야말로 질문이 필요한
|
|
4394
|
+
경우다 — `--impacted` 는 답변과 **독립된 입력**이고
|
|
4395
|
+
`incremental_scope._decision_for_run` 은 answered 가 비어도 impacted 만으로
|
|
4396
|
+
`incremental` 을 낸다. 열린 항목이 하나도 남지 않은 리포트를 재실행하면서
|
|
4397
|
+
한 stage 만 다시 보고 싶은 경우가 정확히 그 형태이고, 종전 게이트는 그때
|
|
4398
|
+
질문을 없애 사용자가 CLI 플래그를 직접 쓰는 것 말고는 길이 없게 만들었다.
|
|
4399
|
+
|
|
4400
|
+
남는 배제 사유는 하나 — Stage Map 을 못 읽으면 입력받은 번호를 검증할 수단이
|
|
4401
|
+
없다(`_prior_stage_numbers` 가 드는 `no_stage_map`). 그때만 묻지 않는다.
|
|
4402
|
+
|
|
4403
|
+
**base ref(C1)는 여기서 안 본다**: `preview_link_availability` 는 이름 그대로
|
|
4404
|
+
SHA 없이 판정하고, 현재 base SHA 는 `render-bundle` 이 매니페스트를 쓴 뒤에야
|
|
4405
|
+
존재한다. base ref 가 움직인 재실행에서도 질문은 그대로 나오고, full 판정은
|
|
4406
|
+
나중에 `decide_scope` 가 내린다.
|
|
4407
|
+
"""
|
|
4408
|
+
if _reverify_scope_preview(state) is None:
|
|
4409
|
+
return False
|
|
4410
|
+
if _reverify_scope_shape(state) != SCOPE_SHAPE_NO_ANSWERS:
|
|
4411
|
+
return True
|
|
4412
|
+
return _prior_stage_map_readable(state)
|
|
4161
4413
|
|
|
4162
4414
|
|
|
4163
4415
|
def _reverify_scope_step_pending(state: WizardState) -> bool:
|
|
@@ -4190,26 +4442,77 @@ def _prior_stage_numbers(state: WizardState) -> set[int]:
|
|
|
4190
4442
|
return stages
|
|
4191
4443
|
|
|
4192
4444
|
|
|
4445
|
+
_SCOPE_SHAPE_LABEL_KEY = {
|
|
4446
|
+
SCOPE_SHAPE_UNLINKED: "label_unlinked",
|
|
4447
|
+
SCOPE_SHAPE_NO_ANSWERS: "label_no_answers",
|
|
4448
|
+
}
|
|
4449
|
+
|
|
4450
|
+
|
|
4193
4451
|
def _build_reverify_scope_pick(state: WizardState) -> Prompt:
|
|
4194
4452
|
t = _p(state.workspace_root, "reverify_scope_pick")
|
|
4195
4453
|
opts = t["options"]
|
|
4196
|
-
|
|
4197
|
-
|
|
4198
|
-
|
|
4199
|
-
|
|
4200
|
-
|
|
4201
|
-
|
|
4202
|
-
|
|
4454
|
+
shape = _reverify_scope_shape(state)
|
|
4455
|
+
if shape == SCOPE_SHAPE_LINKED:
|
|
4456
|
+
options = [
|
|
4457
|
+
_opt("auto", opts["auto"]),
|
|
4458
|
+
_opt("full", opts["full"]),
|
|
4459
|
+
_opt(PICK_TYPE_CUSTOM, opts[PICK_TYPE_CUSTOM]),
|
|
4460
|
+
]
|
|
4461
|
+
# 아래 두 모양에서 `auto` 를 빼는 이유는 같다 — 되짚기가 stage 를 못 냈으므로
|
|
4462
|
+
# 그 핀은 CLI 에 빈 `--impacted` 를 보내고 full(또는 `unresolved`)로만
|
|
4463
|
+
# 돌아온다. 고를 수 있는 것처럼 두면 추천이 아니라 함정이 된다.
|
|
4464
|
+
#
|
|
4465
|
+
# `carry-all` 이 그 자리를 메운다. stage 를 **추가만** 하는 재실행에서는
|
|
4466
|
+
# 지정할 번호가 없어 직접 입력도 쓸 수 없고, 남는 선택이 `full` 뿐이면
|
|
4467
|
+
# 이월해도 되는 stage 를 전부 다시 교차검증하는 비용을 치른다.
|
|
4468
|
+
elif shape == SCOPE_SHAPE_UNLINKED:
|
|
4469
|
+
# 되짚기가 stage 를 못 냈으므로 좁힐 근거가 없다 — 이 모양에서 안전한
|
|
4470
|
+
# 기본값은 full 이고, 그것이 추천이다. 직접 입력은 마지막이다: 목록이
|
|
4471
|
+
# 직접 입력으로 열리면 질문을 통째로 되돌려준 것이고, 실측에서 그
|
|
4472
|
+
# 화면은 리드가 preamble 에 stage 번호를 이미 적어 놓고도 자유 입력만
|
|
4473
|
+
# 내놓았다.
|
|
4474
|
+
options = [
|
|
4475
|
+
_opt("full", opts["full_recommended"]),
|
|
4476
|
+
_opt(CARRY_ALL_SCOPE, opts[CARRY_ALL_SCOPE]),
|
|
4477
|
+
_opt(PICK_TYPE_CUSTOM, opts[PICK_TYPE_CUSTOM]),
|
|
4478
|
+
]
|
|
4203
4479
|
else:
|
|
4204
|
-
|
|
4205
|
-
|
|
4206
|
-
|
|
4480
|
+
# 답변된 clarification 이 0건인 재실행. 사용자가 목적을 갖고 stage 를
|
|
4481
|
+
# 지정하러 온 자리라 full 을 추천으로 올리면 비싼 쪽으로 몰게 된다 —
|
|
4482
|
+
# 추천 없이 두고, 근거가 없다는 사실을 라벨이 말한다.
|
|
4483
|
+
options = [
|
|
4484
|
+
_opt(CARRY_ALL_SCOPE, opts[CARRY_ALL_SCOPE]),
|
|
4485
|
+
_opt("full", opts["full"]),
|
|
4486
|
+
_opt(PICK_TYPE_CUSTOM, opts[PICK_TYPE_CUSTOM]),
|
|
4487
|
+
]
|
|
4488
|
+
label = t[_SCOPE_SHAPE_LABEL_KEY.get(shape, "label")] + _scope_pick_context(state, shape, t)
|
|
4207
4489
|
return Prompt(
|
|
4208
4490
|
step=S_REVERIFY_SCOPE_PICK, kind="pick", label=label,
|
|
4209
4491
|
options=options,
|
|
4210
4492
|
echo_template=t["echo_template"])
|
|
4211
4493
|
|
|
4212
4494
|
|
|
4495
|
+
def _scope_pick_context(state: WizardState, shape: str, t: dict) -> str:
|
|
4496
|
+
"""질문이 왜 나왔는지 화면에 싣는 한 문장.
|
|
4497
|
+
|
|
4498
|
+
`unlinked` 는 어느 id 가 연결되지 않았는지가 답을 고르는 근거인데, 종전에는
|
|
4499
|
+
그 목록이 `errors.unlinked_auto`(자동을 고른 뒤에야 보이는 문구)에만 있었다.
|
|
4500
|
+
id 없이 "연결되지 않은 것이 있습니다" 만 받은 사용자는 자기 답변 중 무엇을
|
|
4501
|
+
말하는지 모른 채 stage 번호를 대야 했다.
|
|
4502
|
+
|
|
4503
|
+
`no-answers` 는 추천할 근거가 없다는 사실 자체를 말한다 — 근거 없이 한
|
|
4504
|
+
선택지를 추천으로 꾸미지 않는다.
|
|
4505
|
+
"""
|
|
4506
|
+
labels = t.get("labels") or {}
|
|
4507
|
+
if shape == SCOPE_SHAPE_UNLINKED:
|
|
4508
|
+
ids = (_reverify_scope_preview(state) or {}).get("unlinkedIds") or []
|
|
4509
|
+
note = labels.get("unlinked_ids", "")
|
|
4510
|
+
return note.format(ids=", ".join(str(i) for i in ids)) if note and ids else ""
|
|
4511
|
+
if shape == SCOPE_SHAPE_NO_ANSWERS:
|
|
4512
|
+
return labels.get("no_basis", "")
|
|
4513
|
+
return ""
|
|
4514
|
+
|
|
4515
|
+
|
|
4213
4516
|
def _submit_reverify_scope_pick(state: WizardState, value: str) -> Optional[str]:
|
|
4214
4517
|
t = _p(state.workspace_root, "reverify_scope_pick")
|
|
4215
4518
|
picked = value.strip().lower()
|
|
@@ -4217,22 +4520,40 @@ def _submit_reverify_scope_pick(state: WizardState, value: str) -> Optional[str]
|
|
|
4217
4520
|
state.reverify_scope = ""
|
|
4218
4521
|
state.reverify_scope_pending_text = True
|
|
4219
4522
|
return "reverify-scope: 직접 입력"
|
|
4220
|
-
if picked not in ("auto", "full"):
|
|
4523
|
+
if picked not in ("auto", "full", CARRY_ALL_SCOPE):
|
|
4221
4524
|
raise WizardError(
|
|
4222
|
-
f"expected 'auto' / 'full' / {
|
|
4525
|
+
f"expected 'auto' / 'full' / {CARRY_ALL_SCOPE!r} / "
|
|
4526
|
+
f"{PICK_TYPE_CUSTOM!r}, got: {value!r}"
|
|
4223
4527
|
)
|
|
4224
4528
|
if picked == "auto":
|
|
4225
|
-
|
|
4226
|
-
unlinked = preview.get("unlinkedIds") or []
|
|
4227
|
-
if unlinked:
|
|
4228
|
-
raise WizardError(
|
|
4229
|
-
t["errors"]["unlinked_auto"].format(ids=", ".join(unlinked))
|
|
4230
|
-
)
|
|
4529
|
+
_refuse_auto_without_a_back_trace(state, t)
|
|
4231
4530
|
state.reverify_scope = picked
|
|
4232
4531
|
state.reverify_scope_pending_text = False
|
|
4233
4532
|
return t["echo_suffixes"][picked]
|
|
4234
4533
|
|
|
4235
4534
|
|
|
4535
|
+
def _refuse_auto_without_a_back_trace(
|
|
4536
|
+
state: WizardState,
|
|
4537
|
+
t: dict,
|
|
4538
|
+
*,
|
|
4539
|
+
auto_key: str = "unlinked_auto",
|
|
4540
|
+
no_answers_key: str = "no_answers_auto",
|
|
4541
|
+
) -> None:
|
|
4542
|
+
"""되짚기가 stage 를 못 낸 재실행에서 `auto` 를 거절한다.
|
|
4543
|
+
|
|
4544
|
+
두 모양의 사유가 다르므로 문구도 다르다 — 연결 안 된 id 는 그 id 를 이름으로
|
|
4545
|
+
말해 줘야 사용자가 무엇의 stage 를 지정할지 알고, 답변이 아예 없는 경우에는
|
|
4546
|
+
말할 id 자체가 없다. 두 진입점(picker 의 `auto`, stage 입력의 빈 줄)이 같은
|
|
4547
|
+
판정을 쓰도록 한곳에 둔다.
|
|
4548
|
+
"""
|
|
4549
|
+
shape = _reverify_scope_shape(state)
|
|
4550
|
+
if shape == SCOPE_SHAPE_UNLINKED:
|
|
4551
|
+
unlinked = (_reverify_scope_preview(state) or {}).get("unlinkedIds") or []
|
|
4552
|
+
raise WizardError(t["errors"][auto_key].format(ids=", ".join(unlinked)))
|
|
4553
|
+
if shape == SCOPE_SHAPE_NO_ANSWERS:
|
|
4554
|
+
raise WizardError(t["errors"][no_answers_key])
|
|
4555
|
+
|
|
4556
|
+
|
|
4236
4557
|
def _build_reverify_scope_stages(state: WizardState) -> Prompt:
|
|
4237
4558
|
t = _p(state.workspace_root, "reverify_scope_stages")
|
|
4238
4559
|
return Prompt(
|
|
@@ -4244,12 +4565,11 @@ def _submit_reverify_scope_stages(state: WizardState, value: str) -> Optional[st
|
|
|
4244
4565
|
t = _p(state.workspace_root, "reverify_scope_stages")
|
|
4245
4566
|
tokens = [token.strip() for token in value.split(",") if token.strip()]
|
|
4246
4567
|
if not tokens:
|
|
4247
|
-
|
|
4248
|
-
|
|
4249
|
-
|
|
4250
|
-
|
|
4251
|
-
|
|
4252
|
-
)
|
|
4568
|
+
# 빈 줄은 `auto` 로 떨어지므로 picker 의 `auto` 와 같은 배제가 걸린다 —
|
|
4569
|
+
# 여기만 열어 두면 picker 에서 막은 핀이 뒷문으로 들어온다.
|
|
4570
|
+
_refuse_auto_without_a_back_trace(
|
|
4571
|
+
state, t, auto_key="unlinked_empty", no_answers_key="no_answers_empty"
|
|
4572
|
+
)
|
|
4253
4573
|
state.reverify_scope = "auto"
|
|
4254
4574
|
state.reverify_scope_pending_text = False
|
|
4255
4575
|
return t["echo_suffixes"]["auto"]
|
|
@@ -4502,8 +4822,24 @@ def _build_reuse_previous(state: WizardState) -> Prompt:
|
|
|
4502
4822
|
|
|
4503
4823
|
|
|
4504
4824
|
def _restore_reused_lead(state: WizardState, inputs: dict) -> None:
|
|
4505
|
-
|
|
4825
|
+
"""리드는 지금 host 에서 파생한다. 직전 run 의 기록을 물려받지 않는다.
|
|
4826
|
+
|
|
4827
|
+
run-inputs 의 ``leadProvider`` 는 그 run 이 어느 host 에서 돌았는지의 기록이지
|
|
4828
|
+
사용자가 고른 값이 아니다. wizard 에는 리드 provider 를 묻는 단계 자체가 없고
|
|
4829
|
+
``resolve_lead_provider`` 가 host 의 네이티브 provider 로 파생한다. 그 기록을
|
|
4830
|
+
그대로 요청으로 되먹이면 Codex 에서 돌린 run 을 Claude Code 에서 이어받을 때
|
|
4831
|
+
host 검사에 걸려 brief/workers/모델 복원까지 통째로 막힌다.
|
|
4832
|
+
|
|
4833
|
+
네이티브 리드가 없는 host(``external``) 에서만 기록된 값이 유일한 단서이므로
|
|
4834
|
+
그때만 참고한다.
|
|
4835
|
+
"""
|
|
4506
4836
|
try:
|
|
4837
|
+
host = default_host_registry().resolve(state.host_runtime).descriptor
|
|
4838
|
+
requested_provider = (
|
|
4839
|
+
""
|
|
4840
|
+
if host.native_provider_id
|
|
4841
|
+
else _safe_provider(inputs.get("leadProvider"), "lead")
|
|
4842
|
+
)
|
|
4507
4843
|
assignment = resolve_lead_provider(
|
|
4508
4844
|
host_id=state.host_runtime,
|
|
4509
4845
|
requested_provider=requested_provider,
|
|
@@ -5410,6 +5746,12 @@ STEPS: list[Step] = [
|
|
|
5410
5746
|
and S_APPROVE_PLAN_CONFIRM not in s.answered),
|
|
5411
5747
|
build=_build_approve_plan_confirm, submit=_submit_approve_plan_confirm,
|
|
5412
5748
|
owns=("approve_plan_candidate",)),
|
|
5749
|
+
Step(S_STAGE_PICK,
|
|
5750
|
+
applies=lambda s: (s.task_type in _STAGE_SCOPED_TASK_TYPES
|
|
5751
|
+
and bool(s.approved_plan_path)
|
|
5752
|
+
and S_STAGE_PICK not in s.answered),
|
|
5753
|
+
build=_build_stage_pick, submit=_submit_stage_pick,
|
|
5754
|
+
owns=("selected_stage", "selected_stages")),
|
|
5413
5755
|
Step(S_DESIGN_PREP_DECISION,
|
|
5414
5756
|
applies=_design_prep_decision_applies,
|
|
5415
5757
|
build=_build_design_prep_decision,
|
|
@@ -5431,12 +5773,6 @@ STEPS: list[Step] = [
|
|
|
5431
5773
|
owns=("design_prep_decision", "design_prep_overrides_json",
|
|
5432
5774
|
"design_prep_notes"),
|
|
5433
5775
|
repeatable=True),
|
|
5434
|
-
Step(S_STAGE_PICK,
|
|
5435
|
-
applies=lambda s: (s.task_type in _STAGE_SCOPED_TASK_TYPES
|
|
5436
|
-
and bool(s.approved_plan_path)
|
|
5437
|
-
and S_STAGE_PICK not in s.answered),
|
|
5438
|
-
build=_build_stage_pick, submit=_submit_stage_pick,
|
|
5439
|
-
owns=("selected_stage", "selected_stages")),
|
|
5440
5776
|
Step(S_HANDOFF_STAGE_PICK,
|
|
5441
5777
|
applies=lambda s: (s.task_type == "release-handoff"
|
|
5442
5778
|
and bool(s.task_group)
|
|
@@ -5870,6 +6206,18 @@ def init_state(
|
|
|
5870
6206
|
)
|
|
5871
6207
|
|
|
5872
6208
|
|
|
6209
|
+
def _auto_start_new_task_when_it_is_the_only_choice(state: WizardState) -> None:
|
|
6210
|
+
"""미완료 task 가 없으면 중복된 새 작업 선택 화면을 건너뛴다."""
|
|
6211
|
+
prompt = next_prompt(state)
|
|
6212
|
+
if (
|
|
6213
|
+
prompt.step != S_TASK_PICK
|
|
6214
|
+
or [option.value for option in prompt.options] != [TASK_PICK_NEW_TOKEN]
|
|
6215
|
+
):
|
|
6216
|
+
return
|
|
6217
|
+
_submit_task_pick(state, TASK_PICK_NEW_TOKEN)
|
|
6218
|
+
state.answered.append(S_TASK_PICK)
|
|
6219
|
+
|
|
6220
|
+
|
|
5873
6221
|
def _build_group_prompt(state: WizardState, group_id: str) -> Prompt:
|
|
5874
6222
|
"""그룹의 적용가능·미답변 픽 멤버를 최대 GROUP_MAX_TABS 개 모은다.
|
|
5875
6223
|
|
|
@@ -6313,6 +6661,9 @@ def _reverify_scope_line(state: WizardState) -> Optional[str]:
|
|
|
6313
6661
|
if state.reverify_scope == "full":
|
|
6314
6662
|
return _msg(state.workspace_root, "confirmation",
|
|
6315
6663
|
"reverify_scope_user_full")
|
|
6664
|
+
if state.reverify_scope == CARRY_ALL_SCOPE:
|
|
6665
|
+
return _msg(state.workspace_root, "confirmation",
|
|
6666
|
+
"reverify_scope_user_carry_all")
|
|
6316
6667
|
if state.reverify_scope and state.reverify_scope != "auto":
|
|
6317
6668
|
return _msg(state.workspace_root, "confirmation",
|
|
6318
6669
|
"reverify_scope_user_stages", stages=state.reverify_scope)
|
|
@@ -6378,15 +6729,22 @@ def confirmation_block(state: WizardState) -> str:
|
|
|
6378
6729
|
lines.append(f" workers : {roster}")
|
|
6379
6730
|
if role_selection:
|
|
6380
6731
|
profile = _load_role_profile_for_state(state)
|
|
6381
|
-
|
|
6382
|
-
|
|
6383
|
-
|
|
6384
|
-
|
|
6385
|
-
|
|
6386
|
-
|
|
6387
|
-
|
|
6388
|
-
|
|
6389
|
-
|
|
6732
|
+
native_provider = default_host_registry().resolve(
|
|
6733
|
+
state.host_runtime
|
|
6734
|
+
).descriptor.native_provider_id
|
|
6735
|
+
leader_model = (
|
|
6736
|
+
"current-session"
|
|
6737
|
+
if state.host_entry_mode == "current-session"
|
|
6738
|
+
else f"{native_provider} (host runtime default)"
|
|
6739
|
+
)
|
|
6740
|
+
lines.append(_msg(
|
|
6741
|
+
state.workspace_root,
|
|
6742
|
+
"confirmation",
|
|
6743
|
+
"static_role",
|
|
6744
|
+
role="leader",
|
|
6745
|
+
ordinal="1",
|
|
6746
|
+
model=leader_model,
|
|
6747
|
+
))
|
|
6390
6748
|
for requirement in _selectable_static_requirements(state, profile):
|
|
6391
6749
|
count = _selected_role_count(state, requirement)
|
|
6392
6750
|
selected = state.role_models.get(requirement.role, [])
|
|
@@ -6715,6 +7073,7 @@ def _cli(argv: list[str]) -> int:
|
|
|
6715
7073
|
)
|
|
6716
7074
|
if args.critic:
|
|
6717
7075
|
state.critic = args.critic
|
|
7076
|
+
_auto_start_new_task_when_it_is_the_only_choice(state)
|
|
6718
7077
|
save_state_file(state_path, state)
|
|
6719
7078
|
first = next_prompt(state)
|
|
6720
7079
|
print(json.dumps({"ok": True, "next": prompt_payload(state, first)},
|
|
@@ -6750,7 +7109,13 @@ def _cli(argv: list[str]) -> int:
|
|
|
6750
7109
|
except WizardError as exc:
|
|
6751
7110
|
_save_resumed_state_file(state_path, persisted_state)
|
|
6752
7111
|
try:
|
|
6753
|
-
current
|
|
7112
|
+
# current 는 방금 디스크로 되돌린 상태에서 렌더한다. 뮤테이션이
|
|
7113
|
+
# 진행되다 만 state 로 렌더하면, 검증 전에 필드를 채운 핸들러
|
|
7114
|
+
# (예: reuse_previous)의 실패가 다음 스텝을 current 로 내보내
|
|
7115
|
+
# 재질문 계약("ok:false 면 같은 step 재제출")과 어긋난다.
|
|
7116
|
+
current = prompt_payload(
|
|
7117
|
+
persisted_state, next_prompt(persisted_state)
|
|
7118
|
+
)
|
|
6754
7119
|
except WizardError:
|
|
6755
7120
|
# 현재 step 의 build 자체가 실패하면(예: 손상된 Stage Map 으로
|
|
6756
7121
|
# _build_stage_pick·_build_handoff_stage_pick 가 raise) recovery 재렌더가
|