okstra 0.186.6 → 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 +487 -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 +32 -7
- package/runtime/python/okstra_ctl/report_finalize.py +206 -15
- package/runtime/python/okstra_ctl/report_html/common.py +4 -9
- package/runtime/python/okstra_ctl/report_html/render.py +11 -12
- package/runtime/python/okstra_ctl/report_html/view_models/implementation_planning.py +2 -5
- 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 +13 -26
- package/runtime/schemas/final-report-v3.0.schema.json +177 -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/html/base.template.html +13 -0
- package/runtime/templates/reports/html/i18n/en.json +19 -0
- package/runtime/templates/reports/html/i18n/ko.json +19 -0
- package/runtime/templates/reports/html/tasks/final-verification.template.html +2 -2
- package/runtime/templates/reports/html/tasks/implementation-planning.template.html +19 -9
- package/runtime/templates/reports/html/tasks/implementation.template.html +1 -1
- package/runtime/templates/reports/implementation-input.template.md +7 -1
- package/runtime/templates/reports/implementation-planning-input.template.md +4 -0
- package/runtime/templates/reports/quick-input.template.md +2 -0
- package/runtime/templates/reports/release-handoff-input.template.md +6 -1
- package/runtime/templates/reports/report.js +20 -3
- package/runtime/templates/reports/schedule.template.md +2 -0
- package/runtime/templates/reports/settings.template.json +0 -10
- package/runtime/templates/reports/task-brief.template.md +3 -0
- package/runtime/templates/reports/user-response.template.md +7 -3
- package/runtime/validators/checks/fixtures-01.py +57 -0
- package/runtime/validators/checks/fixtures-02.py +565 -0
- package/runtime/validators/checks/runners-01.py +108 -0
- package/runtime/validators/checks/validate-assets-01.py +60 -0
- package/runtime/validators/checks/validate-prompt-metadata-01.py +261 -0
- package/runtime/validators/checks/validate-tasks-01.py +46 -0
- package/runtime/validators/checks/validate-tasks-02.py +85 -0
- package/runtime/validators/checks/validate-tasks-03.py +61 -0
- package/runtime/validators/checks/validate-tasks-04.py +118 -0
- package/runtime/validators/forbidden_actions.py +73 -3
- package/runtime/validators/lib/common.sh +5 -0
- package/runtime/validators/lib/fixtures.sh +7 -591
- package/runtime/validators/lib/paths.sh +13 -0
- package/runtime/validators/lib/runners.sh +6 -104
- package/runtime/validators/lib/summary.sh +1 -1
- package/runtime/validators/lib/validate-assets.sh +6 -56
- package/runtime/validators/lib/validate-prompt-metadata.sh +6 -257
- package/runtime/validators/lib/validate-tasks.sh +9 -294
- package/runtime/validators/validate-implementation-plan-stages.py +4 -4
- package/runtime/validators/validate-run.py +1369 -2654
- package/runtime/validators/validate-workflow.sh +56 -16
- package/runtime/validators/validate_improvement_report.py +2 -1
- package/runtime/validators/validate_session_conformance.py +295 -49
- package/dist/commands/execute/agent-prompt.d.mts +0 -1
- package/dist/commands/execute/agent-prompt.mjs +0 -24
- package/dist/commands/execute/agent-prompt.mjs.map +0 -1
- package/dist/commands/execute/codex-dispatch.d.mts +0 -3
- package/dist/commands/execute/codex-dispatch.mjs +0 -6
- package/dist/commands/execute/codex-dispatch.mjs.map +0 -1
- package/dist/commands/execute/codex-run.d.mts +0 -3
- package/dist/commands/execute/codex-run.mjs +0 -62
- package/dist/commands/execute/codex-run.mjs.map +0 -1
- package/dist/commands/execute/convergence.d.mts +0 -1
- package/dist/commands/execute/convergence.mjs +0 -38
- package/dist/commands/execute/convergence.mjs.map +0 -1
- package/dist/commands/execute/error-log.d.mts +0 -1
- package/dist/commands/execute/error-log.mjs +0 -18
- package/dist/commands/execute/error-log.mjs.map +0 -1
- package/dist/commands/execute/git-reconcile.d.mts +0 -1
- package/dist/commands/execute/git-reconcile.mjs +0 -30
- package/dist/commands/execute/git-reconcile.mjs.map +0 -1
- package/dist/commands/execute/handoff.d.mts +0 -1
- package/dist/commands/execute/handoff.mjs +0 -31
- package/dist/commands/execute/handoff.mjs.map +0 -1
- package/dist/commands/execute/incremental-carry.d.mts +0 -1
- package/dist/commands/execute/incremental-carry.mjs +0 -20
- package/dist/commands/execute/incremental-carry.mjs.map +0 -1
- package/dist/commands/execute/incremental-scope.d.mts +0 -1
- package/dist/commands/execute/incremental-scope.mjs +0 -29
- package/dist/commands/execute/incremental-scope.mjs.map +0 -1
- package/dist/commands/execute/integrate-stages.d.mts +0 -1
- package/dist/commands/execute/integrate-stages.mjs +0 -24
- package/dist/commands/execute/integrate-stages.mjs.map +0 -1
- package/dist/commands/execute/pane-title.d.mts +0 -1
- package/dist/commands/execute/pane-title.mjs +0 -20
- package/dist/commands/execute/pane-title.mjs.map +0 -1
- package/dist/commands/execute/plan-items.d.mts +0 -1
- package/dist/commands/execute/plan-items.mjs +0 -9
- package/dist/commands/execute/plan-items.mjs.map +0 -1
- package/dist/commands/execute/plan-validate.d.mts +0 -1
- package/dist/commands/execute/plan-validate.mjs +0 -68
- package/dist/commands/execute/plan-validate.mjs.map +0 -1
- package/dist/commands/execute/plan-verify.d.mts +0 -1
- package/dist/commands/execute/plan-verify.mjs +0 -43
- package/dist/commands/execute/plan-verify.mjs.map +0 -1
- package/dist/commands/execute/spawn-followups.d.mts +0 -1
- package/dist/commands/execute/spawn-followups.mjs +0 -22
- package/dist/commands/execute/spawn-followups.mjs.map +0 -1
- package/dist/commands/execute/team.d.mts +0 -3
- package/dist/commands/execute/team.mjs +0 -66
- package/dist/commands/execute/team.mjs.map +0 -1
- package/dist/commands/execute/token-usage.d.mts +0 -1
- package/dist/commands/execute/token-usage.mjs +0 -19
- package/dist/commands/execute/token-usage.mjs.map +0 -1
- package/dist/commands/execute/worker-audit-check.d.mts +0 -1
- package/dist/commands/execute/worker-audit-check.mjs +0 -34
- package/dist/commands/execute/worker-audit-check.mjs.map +0 -1
- package/dist/commands/execute/worker-dispatch.d.mts +0 -7
- package/dist/commands/execute/worker-dispatch.mjs +0 -64
- package/dist/commands/execute/worker-dispatch.mjs.map +0 -1
- package/dist/commands/execute/worker-state.d.mts +0 -1
- package/dist/commands/execute/worker-state.mjs +0 -28
- package/dist/commands/execute/worker-state.mjs.map +0 -1
- package/dist/commands/execute/worktree-lookup.d.mts +0 -1
- package/dist/commands/execute/worktree-lookup.mjs +0 -92
- package/dist/commands/execute/worktree-lookup.mjs.map +0 -1
- package/dist/commands/execute/worktree-status.d.mts +0 -1
- package/dist/commands/execute/worktree-status.mjs +0 -121
- package/dist/commands/execute/worktree-status.mjs.map +0 -1
- package/dist/commands/inspect/code-review.d.mts +0 -1
- package/dist/commands/inspect/code-review.mjs +0 -32
- package/dist/commands/inspect/code-review.mjs.map +0 -1
- package/dist/commands/inspect/container.d.mts +0 -1
- package/dist/commands/inspect/container.mjs +0 -25
- package/dist/commands/inspect/container.mjs.map +0 -1
- package/dist/commands/inspect/context-cost.d.mts +0 -1
- package/dist/commands/inspect/context-cost.mjs +0 -25
- package/dist/commands/inspect/context-cost.mjs.map +0 -1
- package/dist/commands/inspect/design-prep.d.mts +0 -1
- package/dist/commands/inspect/design-prep.mjs +0 -22
- package/dist/commands/inspect/design-prep.mjs.map +0 -1
- package/dist/commands/inspect/error-report.d.mts +0 -1
- package/dist/commands/inspect/error-report.mjs +0 -25
- package/dist/commands/inspect/error-report.mjs.map +0 -1
- package/dist/commands/inspect/error-zip.d.mts +0 -1
- package/dist/commands/inspect/error-zip.mjs +0 -24
- package/dist/commands/inspect/error-zip.mjs.map +0 -1
- package/dist/commands/inspect/log-report.d.mts +0 -1
- package/dist/commands/inspect/log-report.mjs +0 -26
- package/dist/commands/inspect/log-report.mjs.map +0 -1
- package/dist/commands/inspect/model-io.d.mts +0 -1
- package/dist/commands/inspect/model-io.mjs +0 -25
- package/dist/commands/inspect/model-io.mjs.map +0 -1
- package/dist/commands/inspect/profile-show.d.mts +0 -1
- package/dist/commands/inspect/profile-show.mjs +0 -28
- package/dist/commands/inspect/profile-show.mjs.map +0 -1
- package/dist/commands/inspect/recap.d.mts +0 -1
- package/dist/commands/inspect/recap.mjs +0 -30
- package/dist/commands/inspect/recap.mjs.map +0 -1
- package/dist/commands/inspect/resolve-task-key.d.mts +0 -1
- package/dist/commands/inspect/resolve-task-key.mjs +0 -24
- package/dist/commands/inspect/resolve-task-key.mjs.map +0 -1
- package/dist/commands/inspect/rollup.d.mts +0 -1
- package/dist/commands/inspect/rollup.mjs +0 -25
- package/dist/commands/inspect/rollup.mjs.map +0 -1
- package/dist/commands/inspect/run-audit.d.mts +0 -1
- package/dist/commands/inspect/run-audit.mjs +0 -25
- package/dist/commands/inspect/run-audit.mjs.map +0 -1
- package/dist/commands/inspect/set-work-status.d.mts +0 -1
- package/dist/commands/inspect/set-work-status.mjs +0 -29
- package/dist/commands/inspect/set-work-status.mjs.map +0 -1
- package/dist/commands/inspect/stage-map.d.mts +0 -1
- package/dist/commands/inspect/stage-map.mjs +0 -131
- package/dist/commands/inspect/stage-map.mjs.map +0 -1
- package/dist/commands/inspect/task-list.d.mts +0 -1
- package/dist/commands/inspect/task-list.mjs +0 -149
- package/dist/commands/inspect/task-list.mjs.map +0 -1
- package/dist/commands/inspect/task-show.d.mts +0 -1
- package/dist/commands/inspect/task-show.mjs +0 -108
- package/dist/commands/inspect/task-show.mjs.map +0 -1
- package/dist/commands/inspect/time-report.d.mts +0 -1
- package/dist/commands/inspect/time-report.mjs +0 -24
- package/dist/commands/inspect/time-report.mjs.map +0 -1
- package/dist/commands/inspect/usage-report.d.mts +0 -1
- package/dist/commands/inspect/usage-report.mjs +0 -23
- package/dist/commands/inspect/usage-report.mjs.map +0 -1
- package/dist/commands/inspect/user-response.d.mts +0 -1
- package/dist/commands/inspect/user-response.mjs +0 -35
- package/dist/commands/inspect/user-response.mjs.map +0 -1
- package/dist/commands/inspect/worker-liveness.d.mts +0 -1
- package/dist/commands/inspect/worker-liveness.mjs +0 -45
- package/dist/commands/inspect/worker-liveness.mjs.map +0 -1
- package/dist/commands/lifecycle/contract-check.d.mts +0 -1
- package/dist/commands/lifecycle/contract-check.mjs +0 -18
- package/dist/commands/lifecycle/contract-check.mjs.map +0 -1
- package/dist/commands/lifecycle/migrate.d.mts +0 -1
- package/dist/commands/lifecycle/migrate.mjs +0 -30
- package/dist/commands/lifecycle/migrate.mjs.map +0 -1
- package/dist/commands/lifecycle/model.d.mts +0 -1
- package/dist/commands/lifecycle/model.mjs +0 -22
- package/dist/commands/lifecycle/model.mjs.map +0 -1
- package/dist/commands/manager.d.mts +0 -3
- package/dist/commands/manager.mjs +0 -50
- package/dist/commands/manager.mjs.map +0 -1
- package/dist/commands/report/agent-activity.d.mts +0 -1
- package/dist/commands/report/agent-activity.mjs +0 -20
- package/dist/commands/report/agent-activity.mjs.map +0 -1
- package/dist/commands/report/approval-decision.d.mts +0 -1
- package/dist/commands/report/approval-decision.mjs +0 -21
- package/dist/commands/report/approval-decision.mjs.map +0 -1
- package/dist/commands/report/design-snapshot.d.mts +0 -1
- package/dist/commands/report/design-snapshot.mjs +0 -19
- package/dist/commands/report/design-snapshot.mjs.map +0 -1
- package/dist/commands/report/finalize.d.mts +0 -4
- package/dist/commands/report/finalize.mjs +0 -64
- package/dist/commands/report/finalize.mjs.map +0 -1
- package/dist/commands/report/inject-report-index.d.mts +0 -1
- package/dist/commands/report/inject-report-index.mjs +0 -21
- package/dist/commands/report/inject-report-index.mjs.map +0 -1
- package/dist/commands/report/render-final-report.d.mts +0 -1
- package/dist/commands/report/render-final-report.mjs +0 -23
- package/dist/commands/report/render-final-report.mjs.map +0 -1
- package/dist/commands/report/render-views.d.mts +0 -1
- package/dist/commands/report/render-views.mjs +0 -27
- package/dist/commands/report/render-views.mjs.map +0 -1
- package/dist/commands/report/translate.d.mts +0 -1
- package/dist/commands/report/translate.mjs +0 -33
- package/dist/commands/report/translate.mjs.map +0 -1
- package/runtime/bin/lib/okstra/tmux-pane.sh +0 -40
- package/runtime/bin/lib/okstra-ctl/cmd-batch.sh +0 -59
- package/runtime/bin/lib/okstra-ctl/cmd-list.sh +0 -35
- package/runtime/bin/lib/okstra-ctl/cmd-open.sh +0 -36
- package/runtime/bin/lib/okstra-ctl/cmd-projects.sh +0 -26
- package/runtime/bin/lib/okstra-ctl/cmd-reconcile.sh +0 -29
- package/runtime/bin/lib/okstra-ctl/cmd-reindex.sh +0 -38
- package/runtime/bin/lib/okstra-ctl/cmd-rerun.sh +0 -345
- package/runtime/bin/lib/okstra-ctl/cmd-show.sh +0 -27
- package/runtime/bin/lib/okstra-ctl/cmd-tail.sh +0 -92
- package/runtime/bin/lib/okstra-ctl/main.sh +0 -41
- package/runtime/bin/lib/okstra-ctl/prepare.sh +0 -31
- package/runtime/bin/lib/okstra-ctl/usage.sh +0 -23
- package/runtime/bin/okstra-central.sh +0 -152
- package/runtime/bin/okstra-incremental-carry.py +0 -10
- package/runtime/bin/okstra-incremental-scope.py +0 -10
- package/runtime/bin/okstra-team-reconcile.sh +0 -36
- package/runtime/python/okstra_ctl/agent_prompt_cli.py +0 -1156
- package/runtime/python/okstra_ctl/batch.py +0 -60
- package/runtime/python/okstra_ctl/clarification_items.py +0 -1050
- package/runtime/python/okstra_ctl/container_registry.py +0 -72
- package/runtime/python/okstra_ctl/improvement_assignment.py +0 -61
- package/runtime/python/okstra_ctl/resolver.py +0 -54
- package/runtime/python/okstra_ctl/team_reconcile.py +0 -275
- package/runtime/python/okstra_ctl/tmux.py +0 -134
- package/runtime/python/okstra_ctl/worktree.py +0 -1099
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"""runners-01 — 셸 시나리오에서 꺼낸 단언 블록.
|
|
2
|
+
|
|
3
|
+
호출부: validators/lib/runners.sh:44
|
|
4
|
+
규칙 R1 은 실행될 소스를 문자열/heredoc 이 아니라 실파일에 두게 한다.
|
|
5
|
+
이 파일의 내용은 꺼내기 전과 같다 — 옮기기만 했다.
|
|
6
|
+
"""
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
import json
|
|
9
|
+
import subprocess
|
|
10
|
+
import sys
|
|
11
|
+
|
|
12
|
+
project_root = Path(sys.argv[1])
|
|
13
|
+
task_manifest_path = project_root / sys.argv[2]
|
|
14
|
+
validator_script = Path(sys.argv[3])
|
|
15
|
+
expected_status = sys.argv[4]
|
|
16
|
+
expected_failure_substring = sys.argv[5]
|
|
17
|
+
|
|
18
|
+
task_manifest = json.loads(task_manifest_path.read_text())
|
|
19
|
+
timeline_path = project_root / task_manifest["historyTimelinePath"]
|
|
20
|
+
timeline = json.loads(timeline_path.read_text())
|
|
21
|
+
runs = timeline.get("runs", [])
|
|
22
|
+
latest_run = None
|
|
23
|
+
if isinstance(runs, list):
|
|
24
|
+
for item in reversed(runs):
|
|
25
|
+
if isinstance(item, dict):
|
|
26
|
+
latest_run = item
|
|
27
|
+
break
|
|
28
|
+
|
|
29
|
+
if latest_run is None:
|
|
30
|
+
raise SystemExit("timeline does not contain a latest run entry")
|
|
31
|
+
|
|
32
|
+
run_manifest_path = project_root / latest_run["runManifestPath"]
|
|
33
|
+
run_manifest = json.loads(run_manifest_path.read_text())
|
|
34
|
+
team_state_path = project_root / run_manifest["teamStatePath"]
|
|
35
|
+
report_path = project_root / run_manifest["expectedReportRecordPath"]
|
|
36
|
+
final_status_path = project_root / run_manifest["expectedStatusPath"]
|
|
37
|
+
|
|
38
|
+
process = subprocess.run(
|
|
39
|
+
[
|
|
40
|
+
sys.executable,
|
|
41
|
+
str(validator_script),
|
|
42
|
+
"--team-state",
|
|
43
|
+
str(team_state_path),
|
|
44
|
+
"--report",
|
|
45
|
+
str(report_path),
|
|
46
|
+
"--run-manifest",
|
|
47
|
+
str(run_manifest_path),
|
|
48
|
+
"--task-manifest",
|
|
49
|
+
str(task_manifest_path),
|
|
50
|
+
"--final-status",
|
|
51
|
+
str(final_status_path),
|
|
52
|
+
# session-conformance 주입 시드 — prepare_run_validator_fixture 가
|
|
53
|
+
# 만든 합성 lead jsonl 디렉터리 (실제 ~/.claude/projects 미오염).
|
|
54
|
+
"--claude-projects-dir",
|
|
55
|
+
str(project_root / ".claude-projects-fixture"),
|
|
56
|
+
],
|
|
57
|
+
capture_output=True,
|
|
58
|
+
text=True,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
if not process.stdout.strip():
|
|
62
|
+
raise SystemExit("validator did not produce JSON output")
|
|
63
|
+
|
|
64
|
+
payload = json.loads(process.stdout)
|
|
65
|
+
actual_status = payload.get("validationStatus")
|
|
66
|
+
if actual_status != expected_status:
|
|
67
|
+
extra = ""
|
|
68
|
+
if expected_status == "passed":
|
|
69
|
+
listed = payload.get("failures") or []
|
|
70
|
+
extra = "\n" + "\n".join(str(item) for item in listed)
|
|
71
|
+
raise SystemExit(
|
|
72
|
+
f"validator status mismatch: expected {expected_status}, got {actual_status}{extra}"
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
if expected_status == "passed" and process.returncode != 0:
|
|
76
|
+
raise SystemExit(f"validator returned non-zero on success: {process.stderr}")
|
|
77
|
+
if expected_status == "failed" and process.returncode == 0:
|
|
78
|
+
raise SystemExit("validator unexpectedly succeeded")
|
|
79
|
+
|
|
80
|
+
if expected_failure_substring:
|
|
81
|
+
failures = payload.get("failures", [])
|
|
82
|
+
if not isinstance(failures, list) or not any(
|
|
83
|
+
expected_failure_substring in str(item) for item in failures
|
|
84
|
+
):
|
|
85
|
+
raise SystemExit(
|
|
86
|
+
f"validator failure output did not include expected text: {expected_failure_substring}"
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
if not final_status_path.is_file():
|
|
90
|
+
raise SystemExit(f"validator did not write final status file: {final_status_path}")
|
|
91
|
+
|
|
92
|
+
final_status = final_status_path.read_text().strip()
|
|
93
|
+
expected_final_status = "completed" if expected_status == "passed" else "contract-violated"
|
|
94
|
+
if final_status != expected_final_status:
|
|
95
|
+
raise SystemExit(
|
|
96
|
+
f"final status file mismatch: expected {expected_final_status}, got {final_status}"
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
updated_run_manifest = json.loads(run_manifest_path.read_text())
|
|
100
|
+
updated_task_manifest = json.loads(task_manifest_path.read_text())
|
|
101
|
+
updated_team_state = json.loads(team_state_path.read_text())
|
|
102
|
+
|
|
103
|
+
if updated_run_manifest.get("validation", {}).get("status") != expected_status:
|
|
104
|
+
raise SystemExit("run manifest validation status was not updated correctly")
|
|
105
|
+
if updated_task_manifest.get("contractValidation", {}).get("status") != expected_status:
|
|
106
|
+
raise SystemExit("task manifest validation status was not updated correctly")
|
|
107
|
+
if updated_team_state.get("validator", {}).get("status") != expected_status:
|
|
108
|
+
raise SystemExit("team-state validator status was not updated correctly")
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""validate-assets-01 — 셸 시나리오에서 꺼낸 단언 블록.
|
|
2
|
+
|
|
3
|
+
호출부: validators/lib/validate-assets.sh:23
|
|
4
|
+
규칙 R1 은 실행될 소스를 문자열/heredoc 이 아니라 실파일에 두게 한다.
|
|
5
|
+
이 파일의 내용은 꺼내기 전과 같다 — 옮기기만 했다.
|
|
6
|
+
"""
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
import sys
|
|
9
|
+
|
|
10
|
+
agents_source_root = Path(sys.argv[1]) # <repo>/agents
|
|
11
|
+
skills_source_root = Path(sys.argv[2]) # <repo>/skills
|
|
12
|
+
runtime_root = Path(sys.argv[3]) # <repo>/runtime
|
|
13
|
+
validation_mode = sys.argv[4]
|
|
14
|
+
errors = []
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def check(source_path: Path, target_path: Path) -> None:
|
|
18
|
+
if not target_path.is_file():
|
|
19
|
+
errors.append(f"missing build-output asset: {target_path}")
|
|
20
|
+
return
|
|
21
|
+
if validation_mode == "match" and target_path.read_bytes() != source_path.read_bytes():
|
|
22
|
+
errors.append(f"build-output asset does not match source: {target_path}")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# 1. Worker agent files: agents/workers/*-worker.md -> runtime/agents/workers/*-worker.md
|
|
26
|
+
#
|
|
27
|
+
workers_source = agents_source_root / "workers"
|
|
28
|
+
workers_target = runtime_root / "agents" / "workers"
|
|
29
|
+
if not workers_source.is_dir():
|
|
30
|
+
errors.append(f"missing agents/workers source directory: {workers_source}")
|
|
31
|
+
else:
|
|
32
|
+
for source_path in sorted(workers_source.glob("*.md")):
|
|
33
|
+
check(source_path, workers_target / source_path.name)
|
|
34
|
+
|
|
35
|
+
# 2. Lead contract + internal lead resources: prompts/lead/* + prompts/coding-preflight/*
|
|
36
|
+
# -> runtime/prompts/*. These ship as runtime resources (okstra install copies
|
|
37
|
+
# runtime/prompts -> ~/.okstra/prompts), no longer as agent skills.
|
|
38
|
+
prompts_source_root = agents_source_root.parent / "prompts"
|
|
39
|
+
for sub in ("lead", "coding-preflight"):
|
|
40
|
+
sub_root = prompts_source_root / sub
|
|
41
|
+
if not sub_root.is_dir():
|
|
42
|
+
errors.append(f"missing prompts source directory: {sub_root}")
|
|
43
|
+
continue
|
|
44
|
+
for source_path in sorted(sub_root.rglob("*.md")):
|
|
45
|
+
relative = source_path.relative_to(prompts_source_root)
|
|
46
|
+
check(source_path, runtime_root / "prompts" / relative)
|
|
47
|
+
|
|
48
|
+
# 3. Skill packages: skills/<name>/SKILL.md -> runtime/skills/<name>/SKILL.md
|
|
49
|
+
if not skills_source_root.is_dir():
|
|
50
|
+
errors.append(f"missing skills source directory: {skills_source_root}")
|
|
51
|
+
else:
|
|
52
|
+
for skill_dir in sorted(p for p in skills_source_root.iterdir() if p.is_dir()):
|
|
53
|
+
for source_path in sorted(skill_dir.rglob("*.md")):
|
|
54
|
+
relative = source_path.relative_to(skills_source_root)
|
|
55
|
+
check(source_path, runtime_root / "skills" / relative)
|
|
56
|
+
|
|
57
|
+
if errors:
|
|
58
|
+
for error in errors:
|
|
59
|
+
print(error, file=sys.stderr)
|
|
60
|
+
sys.exit(1)
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
"""validate-prompt-metadata-01 — 셸 시나리오에서 꺼낸 단언 블록.
|
|
2
|
+
|
|
3
|
+
호출부: validators/lib/validate-prompt-metadata.sh:10
|
|
4
|
+
규칙 R1 은 실행될 소스를 문자열/heredoc 이 아니라 실파일에 두게 한다.
|
|
5
|
+
이 파일의 내용은 꺼내기 전과 같다 — 옮기기만 했다.
|
|
6
|
+
"""
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
import json
|
|
9
|
+
import sys
|
|
10
|
+
|
|
11
|
+
project_root = Path(sys.argv[1])
|
|
12
|
+
task_manifest_path = project_root / sys.argv[2]
|
|
13
|
+
errors = []
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def load_json(path: Path) -> dict:
|
|
17
|
+
return json.loads(path.read_text())
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def validate_prompt_contract(
|
|
21
|
+
prefix: str,
|
|
22
|
+
worker_ids: list[str],
|
|
23
|
+
prompt_dir: str,
|
|
24
|
+
prompt_map: object,
|
|
25
|
+
required_worker_roles: object,
|
|
26
|
+
) -> None:
|
|
27
|
+
if not prompt_dir:
|
|
28
|
+
errors.append(f"{prefix} is missing worker prompt directory metadata")
|
|
29
|
+
if not isinstance(prompt_map, dict):
|
|
30
|
+
errors.append(f"{prefix} worker prompt map is missing or invalid")
|
|
31
|
+
prompt_map = {}
|
|
32
|
+
if not isinstance(required_worker_roles, list):
|
|
33
|
+
errors.append(f"{prefix} requiredWorkerRoles is missing or invalid")
|
|
34
|
+
required_worker_roles = []
|
|
35
|
+
|
|
36
|
+
expected_dir_prefix = prompt_dir.rstrip("/") + "/" if prompt_dir else ""
|
|
37
|
+
for worker_id in worker_ids:
|
|
38
|
+
if worker_id not in prompt_map:
|
|
39
|
+
errors.append(f"{prefix} worker prompt map is missing selected worker: {worker_id}")
|
|
40
|
+
|
|
41
|
+
for worker in required_worker_roles:
|
|
42
|
+
if not isinstance(worker, dict):
|
|
43
|
+
errors.append(f"{prefix} requiredWorkerRoles contains a non-object entry")
|
|
44
|
+
continue
|
|
45
|
+
worker_id = str(worker.get("workerId", "")).strip()
|
|
46
|
+
prompt_relative = str(worker.get("promptPath", "")).strip()
|
|
47
|
+
if not worker_id:
|
|
48
|
+
errors.append(f"{prefix} requiredWorkerRoles contains an entry without workerId")
|
|
49
|
+
continue
|
|
50
|
+
if not prompt_relative:
|
|
51
|
+
errors.append(
|
|
52
|
+
f"{prefix} required worker is missing promptPath: {worker_id}"
|
|
53
|
+
)
|
|
54
|
+
continue
|
|
55
|
+
if prompt_map.get(worker_id) != prompt_relative:
|
|
56
|
+
errors.append(
|
|
57
|
+
f"{prefix} worker prompt map does not match requiredWorkerRoles for {worker_id}"
|
|
58
|
+
)
|
|
59
|
+
if expected_dir_prefix and not prompt_relative.startswith(expected_dir_prefix):
|
|
60
|
+
errors.append(
|
|
61
|
+
f"{prefix} worker prompt path is outside the prompt directory: {prompt_relative}"
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
if not task_manifest_path.is_file():
|
|
66
|
+
errors.append(f"task manifest is missing: {task_manifest_path}")
|
|
67
|
+
else:
|
|
68
|
+
task_manifest = load_json(task_manifest_path)
|
|
69
|
+
selected_workers = task_manifest.get("recommendedWorkers", [])
|
|
70
|
+
if not isinstance(selected_workers, list):
|
|
71
|
+
errors.append("task manifest recommendedWorkers is missing or invalid")
|
|
72
|
+
selected_workers = []
|
|
73
|
+
|
|
74
|
+
task_prompt_dir = str(task_manifest.get("latestRunPromptsPath", "")).strip()
|
|
75
|
+
if not task_prompt_dir:
|
|
76
|
+
errors.append("task manifest is missing latestRunPromptsPath")
|
|
77
|
+
|
|
78
|
+
task_artifacts = task_manifest.get("artifacts", {})
|
|
79
|
+
if not isinstance(task_artifacts, dict):
|
|
80
|
+
errors.append("task manifest artifacts is missing or invalid")
|
|
81
|
+
task_artifacts = {}
|
|
82
|
+
|
|
83
|
+
validate_prompt_contract(
|
|
84
|
+
"task manifest",
|
|
85
|
+
selected_workers,
|
|
86
|
+
str(task_artifacts.get("workerPromptsDirectoryPath", "")).strip(),
|
|
87
|
+
task_artifacts.get("workerPromptPathByWorkerId"),
|
|
88
|
+
task_manifest.get("resultContract", {}).get("requiredWorkerRoles"),
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
if (
|
|
92
|
+
task_prompt_dir
|
|
93
|
+
and str(task_artifacts.get("workerPromptsDirectoryPath", "")).strip()
|
|
94
|
+
and task_prompt_dir
|
|
95
|
+
!= str(task_artifacts.get("workerPromptsDirectoryPath", "")).strip()
|
|
96
|
+
):
|
|
97
|
+
errors.append(
|
|
98
|
+
"task manifest latestRunPromptsPath does not match artifacts.workerPromptsDirectoryPath"
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
timeline_relative_path = str(
|
|
102
|
+
task_manifest.get("historyTimelinePath", "")
|
|
103
|
+
).strip()
|
|
104
|
+
if not timeline_relative_path:
|
|
105
|
+
errors.append("task manifest is missing historyTimelinePath")
|
|
106
|
+
else:
|
|
107
|
+
timeline_path = project_root / timeline_relative_path
|
|
108
|
+
if not timeline_path.is_file():
|
|
109
|
+
errors.append(f"timeline file is missing: {timeline_path}")
|
|
110
|
+
else:
|
|
111
|
+
timeline = load_json(timeline_path)
|
|
112
|
+
runs = timeline.get("runs", [])
|
|
113
|
+
latest_run = None
|
|
114
|
+
if isinstance(runs, list):
|
|
115
|
+
for item in reversed(runs):
|
|
116
|
+
if isinstance(item, dict):
|
|
117
|
+
latest_run = item
|
|
118
|
+
break
|
|
119
|
+
if latest_run is None:
|
|
120
|
+
errors.append("timeline does not contain a latest run entry")
|
|
121
|
+
else:
|
|
122
|
+
latest_run_prompt_dir = str(
|
|
123
|
+
latest_run.get("workerPromptDirectoryPath", "")
|
|
124
|
+
).strip()
|
|
125
|
+
if not latest_run_prompt_dir:
|
|
126
|
+
errors.append(
|
|
127
|
+
"latest timeline entry is missing workerPromptDirectoryPath"
|
|
128
|
+
)
|
|
129
|
+
if task_prompt_dir and latest_run_prompt_dir and task_prompt_dir != latest_run_prompt_dir:
|
|
130
|
+
errors.append(
|
|
131
|
+
"timeline latest run prompt directory does not match task manifest latestRunPromptsPath"
|
|
132
|
+
)
|
|
133
|
+
prompt_map = latest_run.get("workerPromptPathByWorkerId")
|
|
134
|
+
if not isinstance(prompt_map, dict):
|
|
135
|
+
errors.append(
|
|
136
|
+
"latest timeline entry is missing workerPromptPathByWorkerId"
|
|
137
|
+
)
|
|
138
|
+
else:
|
|
139
|
+
for worker_id in selected_workers:
|
|
140
|
+
if worker_id not in prompt_map:
|
|
141
|
+
errors.append(
|
|
142
|
+
f"latest timeline entry is missing worker prompt path for {worker_id}"
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
run_manifest_relative_path = str(
|
|
146
|
+
latest_run.get("runManifestPath", "")
|
|
147
|
+
).strip()
|
|
148
|
+
if not run_manifest_relative_path:
|
|
149
|
+
errors.append(
|
|
150
|
+
"latest timeline entry is missing runManifestPath"
|
|
151
|
+
)
|
|
152
|
+
else:
|
|
153
|
+
run_manifest_path = project_root / run_manifest_relative_path
|
|
154
|
+
if not run_manifest_path.is_file():
|
|
155
|
+
errors.append(f"latest run manifest is missing: {run_manifest_path}")
|
|
156
|
+
else:
|
|
157
|
+
run_manifest = load_json(run_manifest_path)
|
|
158
|
+
run_prompt_dir = str(
|
|
159
|
+
run_manifest.get("workerPromptsDirectoryPath", "")
|
|
160
|
+
).strip()
|
|
161
|
+
if task_prompt_dir and run_prompt_dir and task_prompt_dir != run_prompt_dir:
|
|
162
|
+
errors.append(
|
|
163
|
+
"run manifest workerPromptsDirectoryPath does not match task manifest latestRunPromptsPath"
|
|
164
|
+
)
|
|
165
|
+
validate_prompt_contract(
|
|
166
|
+
"run manifest",
|
|
167
|
+
selected_workers,
|
|
168
|
+
run_prompt_dir,
|
|
169
|
+
run_manifest.get("workerPromptPathByWorkerId"),
|
|
170
|
+
run_manifest.get("teamContract", {}).get("requiredWorkerRoles"),
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
team_state_relative_path = str(
|
|
174
|
+
run_manifest.get("teamStatePath")
|
|
175
|
+
or task_manifest.get("teamStatePath", "")
|
|
176
|
+
).strip()
|
|
177
|
+
if not team_state_relative_path:
|
|
178
|
+
errors.append(
|
|
179
|
+
"team state relative path is missing from run/task manifest"
|
|
180
|
+
)
|
|
181
|
+
else:
|
|
182
|
+
team_state_path = project_root / team_state_relative_path
|
|
183
|
+
if not team_state_path.is_file():
|
|
184
|
+
errors.append(f"team-state file is missing: {team_state_path}")
|
|
185
|
+
else:
|
|
186
|
+
team_state = load_json(team_state_path)
|
|
187
|
+
team_artifacts = team_state.get("artifacts", {})
|
|
188
|
+
if not isinstance(team_artifacts, dict):
|
|
189
|
+
errors.append(
|
|
190
|
+
"team-state artifacts is missing or invalid"
|
|
191
|
+
)
|
|
192
|
+
team_artifacts = {}
|
|
193
|
+
team_prompt_dir = str(
|
|
194
|
+
team_artifacts.get(
|
|
195
|
+
"workerPromptsDirectoryPath", ""
|
|
196
|
+
)
|
|
197
|
+
).strip()
|
|
198
|
+
if run_prompt_dir and team_prompt_dir and run_prompt_dir != team_prompt_dir:
|
|
199
|
+
errors.append(
|
|
200
|
+
"team-state worker prompt directory does not match run manifest"
|
|
201
|
+
)
|
|
202
|
+
workers = team_state.get("workers", [])
|
|
203
|
+
if not isinstance(workers, list):
|
|
204
|
+
errors.append("team-state workers is missing or invalid")
|
|
205
|
+
else:
|
|
206
|
+
team_workers = {}
|
|
207
|
+
for worker in workers:
|
|
208
|
+
if not isinstance(worker, dict):
|
|
209
|
+
errors.append(
|
|
210
|
+
"team-state workers contains a non-object entry"
|
|
211
|
+
)
|
|
212
|
+
continue
|
|
213
|
+
worker_id = str(worker.get("workerId", "")).strip()
|
|
214
|
+
if not worker_id:
|
|
215
|
+
errors.append(
|
|
216
|
+
"team-state workers contains an entry without workerId"
|
|
217
|
+
)
|
|
218
|
+
continue
|
|
219
|
+
team_workers[worker_id] = worker
|
|
220
|
+
|
|
221
|
+
run_prompt_map = run_manifest.get(
|
|
222
|
+
"workerPromptPathByWorkerId", {}
|
|
223
|
+
)
|
|
224
|
+
if not isinstance(run_prompt_map, dict):
|
|
225
|
+
run_prompt_map = {}
|
|
226
|
+
|
|
227
|
+
for worker_id in selected_workers:
|
|
228
|
+
worker = team_workers.get(worker_id)
|
|
229
|
+
if worker is None:
|
|
230
|
+
errors.append(
|
|
231
|
+
f"team-state is missing selected worker entry: {worker_id}"
|
|
232
|
+
)
|
|
233
|
+
continue
|
|
234
|
+
prompt_relative = str(
|
|
235
|
+
worker.get("promptPath", "")
|
|
236
|
+
).strip()
|
|
237
|
+
if not prompt_relative:
|
|
238
|
+
errors.append(
|
|
239
|
+
f"team-state worker is missing promptPath: {worker_id}"
|
|
240
|
+
)
|
|
241
|
+
continue
|
|
242
|
+
if run_prompt_map.get(worker_id) != prompt_relative:
|
|
243
|
+
errors.append(
|
|
244
|
+
f"team-state worker prompt path does not match run manifest for {worker_id}"
|
|
245
|
+
)
|
|
246
|
+
expected_dir_prefix = (
|
|
247
|
+
run_prompt_dir.rstrip("/") + "/"
|
|
248
|
+
if run_prompt_dir
|
|
249
|
+
else ""
|
|
250
|
+
)
|
|
251
|
+
if expected_dir_prefix and not prompt_relative.startswith(
|
|
252
|
+
expected_dir_prefix
|
|
253
|
+
):
|
|
254
|
+
errors.append(
|
|
255
|
+
f"team-state worker prompt path is outside the run prompt directory: {prompt_relative}"
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
if errors:
|
|
259
|
+
for error in errors:
|
|
260
|
+
print(error, file=sys.stderr)
|
|
261
|
+
sys.exit(1)
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""validate-tasks-01 — 셸 시나리오에서 꺼낸 단언 블록.
|
|
2
|
+
|
|
3
|
+
호출부: validators/lib/validate-tasks.sh:8
|
|
4
|
+
규칙 R1 은 실행될 소스를 문자열/heredoc 이 아니라 실파일에 두게 한다.
|
|
5
|
+
이 파일의 내용은 꺼내기 전과 같다 — 옮기기만 했다.
|
|
6
|
+
"""
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
import sys
|
|
9
|
+
|
|
10
|
+
brief_path = Path(sys.argv[1])
|
|
11
|
+
reference_path = Path(sys.argv[2])
|
|
12
|
+
expected_task_key = sys.argv[3]
|
|
13
|
+
brief_lines = brief_path.read_text().splitlines()
|
|
14
|
+
section_map = {
|
|
15
|
+
"Configuration References and Expected Values": "config",
|
|
16
|
+
"Deployment Manifests and Expected Values": "deployment",
|
|
17
|
+
}
|
|
18
|
+
captured = {"config": [], "deployment": []}
|
|
19
|
+
current_section = None
|
|
20
|
+
|
|
21
|
+
for line in brief_lines:
|
|
22
|
+
if line.startswith("## "):
|
|
23
|
+
current_section = section_map.get(line[3:].strip())
|
|
24
|
+
continue
|
|
25
|
+
if current_section and line.strip():
|
|
26
|
+
captured[current_section].append(line)
|
|
27
|
+
|
|
28
|
+
errors = []
|
|
29
|
+
|
|
30
|
+
if not reference_path.is_file():
|
|
31
|
+
errors.append(f"reference expectations file is missing: {reference_path}")
|
|
32
|
+
reference_content = ""
|
|
33
|
+
else:
|
|
34
|
+
reference_content = reference_path.read_text()
|
|
35
|
+
|
|
36
|
+
for line in captured["config"] + captured["deployment"]:
|
|
37
|
+
if line not in reference_content:
|
|
38
|
+
errors.append(f"reference expectations file is missing brief line: {line}")
|
|
39
|
+
|
|
40
|
+
if f"- Task Key: `{expected_task_key}`" not in reference_content:
|
|
41
|
+
errors.append("reference expectations file is missing the task key header")
|
|
42
|
+
|
|
43
|
+
if errors:
|
|
44
|
+
for error in errors:
|
|
45
|
+
print(error, file=sys.stderr)
|
|
46
|
+
sys.exit(1)
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"""validate-tasks-02 — 셸 시나리오에서 꺼낸 단언 블록.
|
|
2
|
+
|
|
3
|
+
호출부: validators/lib/validate-tasks.sh:22
|
|
4
|
+
규칙 R1 은 실행될 소스를 문자열/heredoc 이 아니라 실파일에 두게 한다.
|
|
5
|
+
이 파일의 내용은 꺼내기 전과 같다 — 옮기기만 했다.
|
|
6
|
+
"""
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
import json
|
|
9
|
+
import sys
|
|
10
|
+
|
|
11
|
+
project_root = Path(sys.argv[1])
|
|
12
|
+
expected_task_key = sys.argv[2]
|
|
13
|
+
task_manifest_relative_path = sys.argv[3]
|
|
14
|
+
expected_reference_relative_path = sys.argv[4]
|
|
15
|
+
expected_task_catalog_relative_path = sys.argv[5]
|
|
16
|
+
errors = []
|
|
17
|
+
|
|
18
|
+
task_manifest_path = project_root / task_manifest_relative_path
|
|
19
|
+
if not task_manifest_path.is_file():
|
|
20
|
+
errors.append(f"task manifest is missing: {task_manifest_path}")
|
|
21
|
+
else:
|
|
22
|
+
task_manifest = json.loads(task_manifest_path.read_text())
|
|
23
|
+
|
|
24
|
+
if task_manifest.get("taskKey") != expected_task_key:
|
|
25
|
+
errors.append("task manifest does not contain the expected task key")
|
|
26
|
+
|
|
27
|
+
if task_manifest.get("referenceExpectationsPath") != expected_reference_relative_path:
|
|
28
|
+
errors.append("task manifest does not expose referenceExpectationsPath")
|
|
29
|
+
|
|
30
|
+
if task_manifest.get("taskCatalogPath") != expected_task_catalog_relative_path:
|
|
31
|
+
errors.append("task manifest does not expose taskCatalogPath")
|
|
32
|
+
|
|
33
|
+
if (
|
|
34
|
+
task_manifest.get("artifacts", {}).get("referenceExpectationsPath")
|
|
35
|
+
!= expected_reference_relative_path
|
|
36
|
+
):
|
|
37
|
+
errors.append("task manifest artifacts do not expose referenceExpectationsPath")
|
|
38
|
+
|
|
39
|
+
timeline_relative_path = task_manifest.get("historyTimelinePath", "")
|
|
40
|
+
if not timeline_relative_path:
|
|
41
|
+
errors.append("task manifest is missing historyTimelinePath")
|
|
42
|
+
else:
|
|
43
|
+
timeline_path = project_root / timeline_relative_path
|
|
44
|
+
if not timeline_path.is_file():
|
|
45
|
+
errors.append(f"timeline file is missing: {timeline_path}")
|
|
46
|
+
else:
|
|
47
|
+
timeline = json.loads(timeline_path.read_text())
|
|
48
|
+
runs = timeline.get("runs", [])
|
|
49
|
+
latest_run = None
|
|
50
|
+
if isinstance(runs, list):
|
|
51
|
+
for item in reversed(runs):
|
|
52
|
+
if isinstance(item, dict):
|
|
53
|
+
latest_run = item
|
|
54
|
+
break
|
|
55
|
+
if latest_run is None:
|
|
56
|
+
errors.append("timeline does not contain a latest run entry")
|
|
57
|
+
else:
|
|
58
|
+
run_manifest_relative_path = latest_run.get("runManifestPath", "")
|
|
59
|
+
if not run_manifest_relative_path:
|
|
60
|
+
errors.append("latest timeline entry is missing runManifestPath")
|
|
61
|
+
else:
|
|
62
|
+
run_manifest_path = project_root / run_manifest_relative_path
|
|
63
|
+
if not run_manifest_path.is_file():
|
|
64
|
+
errors.append(f"latest run manifest is missing: {run_manifest_path}")
|
|
65
|
+
else:
|
|
66
|
+
run_manifest = json.loads(run_manifest_path.read_text())
|
|
67
|
+
if run_manifest.get("taskKey") != expected_task_key:
|
|
68
|
+
errors.append("run manifest does not contain the expected task key")
|
|
69
|
+
if (
|
|
70
|
+
run_manifest.get("referenceExpectationsPath")
|
|
71
|
+
!= expected_reference_relative_path
|
|
72
|
+
):
|
|
73
|
+
errors.append(
|
|
74
|
+
"run manifest does not expose referenceExpectationsPath"
|
|
75
|
+
)
|
|
76
|
+
if (
|
|
77
|
+
run_manifest.get("taskCatalogPath")
|
|
78
|
+
!= expected_task_catalog_relative_path
|
|
79
|
+
):
|
|
80
|
+
errors.append("run manifest does not expose taskCatalogPath")
|
|
81
|
+
|
|
82
|
+
if errors:
|
|
83
|
+
for error in errors:
|
|
84
|
+
print(error, file=sys.stderr)
|
|
85
|
+
sys.exit(1)
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""validate-tasks-03 — 셸 시나리오에서 꺼낸 단언 블록.
|
|
2
|
+
|
|
3
|
+
호출부: validators/lib/validate-tasks.sh:36
|
|
4
|
+
규칙 R1 은 실행될 소스를 문자열/heredoc 이 아니라 실파일에 두게 한다.
|
|
5
|
+
이 파일의 내용은 꺼내기 전과 같다 — 옮기기만 했다.
|
|
6
|
+
"""
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
import json
|
|
9
|
+
import sys
|
|
10
|
+
|
|
11
|
+
project_root = Path(sys.argv[1])
|
|
12
|
+
discovery_path = Path(sys.argv[2])
|
|
13
|
+
expected_task_key = sys.argv[3]
|
|
14
|
+
expected_task_manifest_relative_path = sys.argv[4]
|
|
15
|
+
expected_reference_relative_path = sys.argv[5]
|
|
16
|
+
expected_task_catalog_relative_path = sys.argv[6]
|
|
17
|
+
errors = []
|
|
18
|
+
|
|
19
|
+
if not discovery_path.is_file():
|
|
20
|
+
errors.append(f"latest-task discovery file is missing: {discovery_path}")
|
|
21
|
+
else:
|
|
22
|
+
discovery = json.loads(discovery_path.read_text())
|
|
23
|
+
|
|
24
|
+
if discovery.get("taskKey") != expected_task_key:
|
|
25
|
+
errors.append("latest-task discovery does not point to the expected task key")
|
|
26
|
+
|
|
27
|
+
if discovery.get("taskManifestPath") != expected_task_manifest_relative_path:
|
|
28
|
+
errors.append("latest-task discovery does not point to the expected task manifest")
|
|
29
|
+
|
|
30
|
+
if discovery.get("referenceExpectationsPath") != expected_reference_relative_path:
|
|
31
|
+
errors.append("latest-task discovery does not expose the expected reference expectations path")
|
|
32
|
+
|
|
33
|
+
if discovery.get("taskCatalogPath") != expected_task_catalog_relative_path:
|
|
34
|
+
errors.append("latest-task discovery does not expose taskCatalogPath")
|
|
35
|
+
|
|
36
|
+
latest_run_prompts_relative_path = discovery.get("latestRunPromptsPath", "")
|
|
37
|
+
if not latest_run_prompts_relative_path:
|
|
38
|
+
errors.append("latest-task discovery is missing latestRunPromptsPath")
|
|
39
|
+
|
|
40
|
+
latest_run_manifest_relative_path = discovery.get("latestRunManifestPath", "")
|
|
41
|
+
if not latest_run_manifest_relative_path:
|
|
42
|
+
errors.append("latest-task discovery is missing latestRunManifestPath")
|
|
43
|
+
else:
|
|
44
|
+
latest_run_manifest_path = project_root / latest_run_manifest_relative_path
|
|
45
|
+
if not latest_run_manifest_path.is_file():
|
|
46
|
+
errors.append(f"latest run manifest is missing: {latest_run_manifest_path}")
|
|
47
|
+
else:
|
|
48
|
+
run_manifest = json.loads(latest_run_manifest_path.read_text())
|
|
49
|
+
if (
|
|
50
|
+
latest_run_prompts_relative_path
|
|
51
|
+
and run_manifest.get("workerPromptsDirectoryPath")
|
|
52
|
+
!= latest_run_prompts_relative_path
|
|
53
|
+
):
|
|
54
|
+
errors.append(
|
|
55
|
+
"latest-task discovery latestRunPromptsPath does not match the latest run manifest"
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
if errors:
|
|
59
|
+
for error in errors:
|
|
60
|
+
print(error, file=sys.stderr)
|
|
61
|
+
sys.exit(1)
|