crewplane 0.1.0__py3-none-any.whl
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.
- crewplane/__init__.py +3 -0
- crewplane/adapters/__init__.py +1 -0
- crewplane/adapters/artifacts/__init__.py +3 -0
- crewplane/adapters/artifacts/filesystem.py +86 -0
- crewplane/adapters/invokers/__init__.py +4 -0
- crewplane/adapters/invokers/cli.py +142 -0
- crewplane/adapters/invokers/cli_invoker/__init__.py +13 -0
- crewplane/adapters/invokers/cli_invoker/capabilities.py +243 -0
- crewplane/adapters/invokers/mock.py +62 -0
- crewplane/adapters/invokers/mock_invoker/__init__.py +4 -0
- crewplane/adapters/invokers/mock_invoker/context.py +44 -0
- crewplane/adapters/invokers/mock_invoker/fixtures.py +42 -0
- crewplane/adapters/invokers/mock_invoker/invoker.py +236 -0
- crewplane/adapters/invokers/mock_invoker/logging.py +80 -0
- crewplane/adapters/invokers/mock_invoker/mutations.py +201 -0
- crewplane/adapters/invokers/mock_invoker/options.py +142 -0
- crewplane/adapters/invokers/mock_invoker/outputs.py +39 -0
- crewplane/adapters/invokers/mock_invoker/selectors.py +86 -0
- crewplane/adapters/ui/__init__.py +4 -0
- crewplane/adapters/ui/null.py +86 -0
- crewplane/adapters/ui/tmux.py +158 -0
- crewplane/architecture/__init__.py +13 -0
- crewplane/architecture/contracts/__init__.py +130 -0
- crewplane/architecture/contracts/integration.py +99 -0
- crewplane/architecture/contracts/integration_options.py +51 -0
- crewplane/architecture/contracts/invocation.py +438 -0
- crewplane/architecture/contracts/json.py +5 -0
- crewplane/architecture/contracts/observer.py +49 -0
- crewplane/architecture/errors.py +17 -0
- crewplane/architecture/loader.py +212 -0
- crewplane/architecture/ports/__init__.py +14 -0
- crewplane/architecture/ports/artifacts.py +161 -0
- crewplane/architecture/ports/invoker.py +29 -0
- crewplane/architecture/ports/runtime.py +41 -0
- crewplane/architecture/ports/ui.py +49 -0
- crewplane/architecture/registry.py +35 -0
- crewplane/artifacts/__init__.py +23 -0
- crewplane/artifacts/atomic.py +93 -0
- crewplane/artifacts/directory_manager.py +151 -0
- crewplane/artifacts/failure_artifacts.py +40 -0
- crewplane/artifacts/generated_files/__init__.py +1 -0
- crewplane/artifacts/generated_files/catalog.py +426 -0
- crewplane/artifacts/generated_files/detection.py +295 -0
- crewplane/artifacts/locks/__init__.py +329 -0
- crewplane/artifacts/locks/manifest.py +178 -0
- crewplane/artifacts/locks/process_identity.py +57 -0
- crewplane/artifacts/manager.py +233 -0
- crewplane/artifacts/naming.py +139 -0
- crewplane/artifacts/results/__init__.py +1 -0
- crewplane/artifacts/results/aggregation.py +136 -0
- crewplane/artifacts/results/findings.py +95 -0
- crewplane/artifacts/results/review_loop_status.py +207 -0
- crewplane/artifacts/results/selection.py +86 -0
- crewplane/artifacts/results/stage_document.py +80 -0
- crewplane/artifacts/results/stage_outputs.py +16 -0
- crewplane/artifacts/results/writer.py +169 -0
- crewplane/artifacts/resume/__init__.py +1 -0
- crewplane/artifacts/resume/decision.py +39 -0
- crewplane/artifacts/resume/generated_files.py +71 -0
- crewplane/artifacts/resume/hydration.py +474 -0
- crewplane/artifacts/resume/validation.py +349 -0
- crewplane/artifacts/run_history.py +218 -0
- crewplane/artifacts/safe_files.py +59 -0
- crewplane/artifacts/workspace/__init__.py +1 -0
- crewplane/artifacts/workspace/bundle_validation.py +450 -0
- crewplane/artifacts/workspace/git_blob_hash.py +98 -0
- crewplane/artifacts/workspace/node_state.py +332 -0
- crewplane/artifacts/workspace/rendered_file_validation.py +302 -0
- crewplane/artifacts/workspace/source_validation.py +399 -0
- crewplane/artifacts/workspace/state/__init__.py +5 -0
- crewplane/artifacts/workspace/state/expected_set.py +172 -0
- crewplane/artifacts/workspace/state/fields.py +50 -0
- crewplane/artifacts/workspace/state/invocations.py +282 -0
- crewplane/artifacts/workspace/state/validation.py +497 -0
- crewplane/bootstrap/__init__.py +11 -0
- crewplane/bootstrap/container.py +113 -0
- crewplane/bootstrap/runtime_config.py +136 -0
- crewplane/cli/__init__.py +3 -0
- crewplane/cli/app.py +422 -0
- crewplane/cli/cleanup.py +300 -0
- crewplane/cli/dry_run.py +182 -0
- crewplane/cli/paths.py +104 -0
- crewplane/cli/run/__init__.py +1 -0
- crewplane/cli/run/best_effort_thread.py +38 -0
- crewplane/cli/run/branch_export_output.py +125 -0
- crewplane/cli/run/components.py +65 -0
- crewplane/cli/run/context.py +62 -0
- crewplane/cli/run/execution.py +298 -0
- crewplane/cli/run/execution_helpers.py +175 -0
- crewplane/cli/run/historical_summary.py +360 -0
- crewplane/cli/run/manifest.py +95 -0
- crewplane/cli/run/observability.py +385 -0
- crewplane/cli/run/preflight.py +378 -0
- crewplane/cli/run/preflight_success.py +89 -0
- crewplane/cli/run/resume.py +300 -0
- crewplane/cli/run/topology.py +51 -0
- crewplane/cli/run/workspace/__init__.py +1 -0
- crewplane/cli/run/workspace/cache_policy.py +67 -0
- crewplane/cli/run/workspace/disk_policy.py +192 -0
- crewplane/cli/run/workspace/filesystem_policy.py +97 -0
- crewplane/cli/run/workspace/git_attributes.py +150 -0
- crewplane/cli/run/workspace/git_index.py +162 -0
- crewplane/cli/run/workspace/git_source.py +330 -0
- crewplane/cli/run/workspace/preflight_diagnostics.py +57 -0
- crewplane/cli/run/workspace/repo_policy.py +420 -0
- crewplane/cli/run/workspace/source_policy.py +322 -0
- crewplane/cli/run/workspace/source_types.py +28 -0
- crewplane/cli/templates.py +75 -0
- crewplane/cli/workflow_runner.py +23 -0
- crewplane/core/__init__.py +5 -0
- crewplane/core/config.py +327 -0
- crewplane/core/execution_state.py +194 -0
- crewplane/core/file_hashing.py +18 -0
- crewplane/core/platform.py +12 -0
- crewplane/core/preflight/__init__.py +67 -0
- crewplane/core/preflight/compile_state.py +259 -0
- crewplane/core/preflight/compiler.py +385 -0
- crewplane/core/preflight/dependency_edges.py +56 -0
- crewplane/core/preflight/diagnostics.py +62 -0
- crewplane/core/preflight/execution_nodes.py +183 -0
- crewplane/core/preflight/fragment_handlers.py +408 -0
- crewplane/core/preflight/input_sources.py +218 -0
- crewplane/core/preflight/models.py +483 -0
- crewplane/core/preflight/plan_contract.py +160 -0
- crewplane/core/preflight/plan_signatures.py +215 -0
- crewplane/core/preflight/prompt_transport_warnings.py +38 -0
- crewplane/core/preflight/references.py +76 -0
- crewplane/core/preflight/render_plans.py +382 -0
- crewplane/core/preflight/runner.py +17 -0
- crewplane/core/preflight/runtime_config/__init__.py +403 -0
- crewplane/core/preflight/runtime_config/redaction.py +314 -0
- crewplane/core/preflight/secrets.py +206 -0
- crewplane/core/preflight/serialization.py +48 -0
- crewplane/core/preflight/signatures.py +11 -0
- crewplane/core/preflight/source.py +156 -0
- crewplane/core/preflight/static_resources.py +179 -0
- crewplane/core/preflight/token_catalog.py +48 -0
- crewplane/core/preflight/validation.py +123 -0
- crewplane/core/preflight/value_fingerprints.py +79 -0
- crewplane/core/preflight/variables.py +7 -0
- crewplane/core/preflight/workspace/__init__.py +1 -0
- crewplane/core/preflight/workspace/files/__init__.py +1 -0
- crewplane/core/preflight/workspace/files/git_reads.py +122 -0
- crewplane/core/preflight/workspace/files/locators.py +412 -0
- crewplane/core/preflight/workspace/files/paths.py +71 -0
- crewplane/core/preflight/workspace/files/selection.py +75 -0
- crewplane/core/preflight/workspace/models.py +96 -0
- crewplane/core/preflight/workspace/observability.py +224 -0
- crewplane/core/preflight/workspace/records.py +63 -0
- crewplane/core/preflight/workspace/snapshot_guard.py +44 -0
- crewplane/core/prompt_segments.py +55 -0
- crewplane/core/provider_names.py +12 -0
- crewplane/core/review_contract.py +62 -0
- crewplane/core/state_paths.py +31 -0
- crewplane/core/token_budget.py +94 -0
- crewplane/core/value_checks.py +11 -0
- crewplane/core/workflow/__init__.py +1 -0
- crewplane/core/workflow/composition/__init__.py +14 -0
- crewplane/core/workflow/composition/imports.py +79 -0
- crewplane/core/workflow/composition/models.py +85 -0
- crewplane/core/workflow/composition/nodes.py +117 -0
- crewplane/core/workflow/composition/parsing.py +86 -0
- crewplane/core/workflow/composition/rewrites.py +102 -0
- crewplane/core/workflow/composition/traversal.py +374 -0
- crewplane/core/workflow/diagnostics.py +38 -0
- crewplane/core/workflow/graph.py +75 -0
- crewplane/core/workflow/keywords.py +59 -0
- crewplane/core/workflow/loading.py +112 -0
- crewplane/core/workflow/markdown/__init__.py +79 -0
- crewplane/core/workflow/markdown/frontmatter.py +68 -0
- crewplane/core/workflow/markdown/markers.py +187 -0
- crewplane/core/workflow/markdown/models.py +284 -0
- crewplane/core/workflow/markdown/payloads.py +102 -0
- crewplane/core/workflow/markdown/sections.py +124 -0
- crewplane/core/workflow/models.py +340 -0
- crewplane/core/workflow/syntax.py +14 -0
- crewplane/core/workflow/validation/__init__.py +35 -0
- crewplane/core/workflow/validation/api.py +145 -0
- crewplane/core/workflow/validation/modes.py +402 -0
- crewplane/core/workflow/validation/nodes.py +156 -0
- crewplane/core/workflow/validation/policies.py +111 -0
- crewplane/core/workflow/validation/templates.py +219 -0
- crewplane/core/workflow/validation/workspace.py +229 -0
- crewplane/core/workflow/validation/workspace_diagnostics.py +322 -0
- crewplane/core/workspace/__init__.py +1 -0
- crewplane/core/workspace/cache.py +14 -0
- crewplane/core/workspace/git_policy.py +222 -0
- crewplane/core/workspace/policy.py +212 -0
- crewplane/core/workspace/selection.py +29 -0
- crewplane/core/workspace/settings.py +170 -0
- crewplane/core/yaml_loader.py +60 -0
- crewplane/example_templates/config.yml +213 -0
- crewplane/example_templates/example-templates/code-review-example.task.md +80 -0
- crewplane/example_templates/example-templates/composition/review-findings-producer-example.task.md +19 -0
- crewplane/example_templates/example-templates/composition/review-fix-composed-example.task.md +38 -0
- crewplane/example_templates/example-templates/composition/review-fix-consumer-example.task.md +49 -0
- crewplane/example_templates/example-templates/design-review-example.task.md +70 -0
- crewplane/example_templates/example-templates/feature-implement-example.task.md +95 -0
- crewplane/example_templates/example-templates/multi-executor-review-chain-example.task.md +74 -0
- crewplane/example_templates/example-templates/refactoring-example.task.md +93 -0
- crewplane/example_templates/example-templates/sample-inputs/coding-standards.md +4 -0
- crewplane/example_templates/example-templates/sample-inputs/feature-brief.md +4 -0
- crewplane/example_templates/example-templates/sample-inputs/review-findings.md +9 -0
- crewplane/example_templates/example-templates/test-generation-example.task.md +87 -0
- crewplane/example_templates/example-templates/worktree/workspace-alternatives-example.task.md +49 -0
- crewplane/example_templates/example-templates/worktree/workspace-inherited-worktree-example.task.md +42 -0
- crewplane/example_templates/single-agent-review.task.md +27 -0
- crewplane/observability/__init__.py +66 -0
- crewplane/observability/dag_graph.py +450 -0
- crewplane/observability/dag_render.py +141 -0
- crewplane/observability/events/__init__.py +81 -0
- crewplane/observability/events/builders.py +215 -0
- crewplane/observability/events/dashboard_state.py +129 -0
- crewplane/observability/events/execution_event.py +80 -0
- crewplane/observability/events/log.py +28 -0
- crewplane/observability/events/payloads.py +199 -0
- crewplane/observability/events/reducer.py +214 -0
- crewplane/observability/events/types.py +40 -0
- crewplane/observability/layout.py +188 -0
- crewplane/observability/log_presentation/__init__.py +26 -0
- crewplane/observability/log_presentation/follow.py +105 -0
- crewplane/observability/log_presentation/formatters.py +380 -0
- crewplane/observability/log_presentation/json_extract.py +348 -0
- crewplane/observability/log_presentation/limits.py +47 -0
- crewplane/observability/log_presentation/models.py +47 -0
- crewplane/observability/log_presentation/sanitize.py +82 -0
- crewplane/observability/log_presentation/tail.py +144 -0
- crewplane/observability/log_presentation/throttle.py +68 -0
- crewplane/observability/log_stream.py +100 -0
- crewplane/observability/node_order.py +29 -0
- crewplane/observability/observer.py +22 -0
- crewplane/observability/observer_lifecycle.py +173 -0
- crewplane/observability/persistent.py +47 -0
- crewplane/observability/render/__init__.py +196 -0
- crewplane/observability/render/cells.py +193 -0
- crewplane/observability/render/header.py +23 -0
- crewplane/observability/render/text.py +26 -0
- crewplane/observability/render/timeline.py +137 -0
- crewplane/observability/render/viewport.py +124 -0
- crewplane/observability/run_summary/accumulator.py +76 -0
- crewplane/observability/run_summary/builder.py +177 -0
- crewplane/observability/run_summary/formatting.py +64 -0
- crewplane/observability/run_summary/issues.py +167 -0
- crewplane/observability/run_summary/logger.py +170 -0
- crewplane/observability/run_summary/markdown.py +391 -0
- crewplane/observability/run_summary/models.py +301 -0
- crewplane/observability/run_summary/spend.py +268 -0
- crewplane/observability/run_summary/terminal.py +204 -0
- crewplane/observability/run_summary/workspace.py +187 -0
- crewplane/observability/run_summary/workspace_readers.py +395 -0
- crewplane/observability/run_summary/workspace_state_paths.py +33 -0
- crewplane/observability/run_summary/workspace_values.py +124 -0
- crewplane/observability/runtime.py +376 -0
- crewplane/observability/status_icons.py +14 -0
- crewplane/observability/text_layout.py +111 -0
- crewplane/observability/timing.py +56 -0
- crewplane/observability/tmux/__init__.py +36 -0
- crewplane/observability/tmux/bindings.py +273 -0
- crewplane/observability/tmux/client.py +141 -0
- crewplane/observability/tmux/commands.py +374 -0
- crewplane/observability/tmux/compact.py +199 -0
- crewplane/observability/tmux/control_state.py +89 -0
- crewplane/observability/tmux/inspect_control.py +189 -0
- crewplane/observability/tmux/inspect_launcher.py +67 -0
- crewplane/observability/tmux/inspect_snapshot.py +71 -0
- crewplane/observability/tmux/labels.py +24 -0
- crewplane/observability/tmux/log_tail.py +165 -0
- crewplane/observability/tmux/refresh.py +362 -0
- crewplane/observability/tmux/rendering.py +406 -0
- crewplane/observability/tmux/runtime.py +15 -0
- crewplane/observability/tmux/runtime_files.py +88 -0
- crewplane/observability/tmux/selected_invocation.py +101 -0
- crewplane/observability/tmux/selection.py +126 -0
- crewplane/observability/tmux/selection_control.py +159 -0
- crewplane/observability/tmux/session.py +43 -0
- crewplane/observability/tmux/session_lifecycle.py +345 -0
- crewplane/observability/tmux/shell_commands.py +76 -0
- crewplane/observability/tmux/viewport.py +77 -0
- crewplane/observability/tmux/window.py +134 -0
- crewplane/observability/types.py +45 -0
- crewplane/py.typed +1 -0
- crewplane/runtime/__init__.py +3 -0
- crewplane/runtime/agent/__init__.py +5 -0
- crewplane/runtime/agent/failures/__init__.py +80 -0
- crewplane/runtime/agent/failures/classifier.py +22 -0
- crewplane/runtime/agent/failures/evidence.py +249 -0
- crewplane/runtime/agent/failures/formatting.py +108 -0
- crewplane/runtime/agent/failures/patterns.py +172 -0
- crewplane/runtime/agent/failures/types.py +73 -0
- crewplane/runtime/agent/invocation/claude_json.py +368 -0
- crewplane/runtime/agent/invocation/command.py +239 -0
- crewplane/runtime/agent/invocation/loop.py +395 -0
- crewplane/runtime/agent/invocation/output.py +295 -0
- crewplane/runtime/agent/invocation/retry.py +228 -0
- crewplane/runtime/agent/invocation/retry_reset.py +32 -0
- crewplane/runtime/agent/invocation/state.py +161 -0
- crewplane/runtime/agent/invocation/telemetry.py +166 -0
- crewplane/runtime/agent/invocation/transitions.py +181 -0
- crewplane/runtime/agent/invoker.py +131 -0
- crewplane/runtime/agent/process/diagnostics.py +70 -0
- crewplane/runtime/agent/process/log_rendering.py +18 -0
- crewplane/runtime/agent/process/runner.py +26 -0
- crewplane/runtime/agent/process/signals.py +84 -0
- crewplane/runtime/agent/process/stream_capture.py +105 -0
- crewplane/runtime/agent/process/streams.py +376 -0
- crewplane/runtime/agent/quota/__init__.py +5 -0
- crewplane/runtime/agent/quota/classifier.py +75 -0
- crewplane/runtime/agent/quota/evidence.py +124 -0
- crewplane/runtime/agent/quota/lexicons.py +251 -0
- crewplane/runtime/agent/quota/waits.py +208 -0
- crewplane/runtime/agent/retry_units.py +49 -0
- crewplane/runtime/agent/usage.py +224 -0
- crewplane/runtime/agent/usage_costs.py +143 -0
- crewplane/runtime/agent/usage_parsing.py +210 -0
- crewplane/runtime/agent/usage_types.py +53 -0
- crewplane/runtime/agent/workspace_environment.py +63 -0
- crewplane/runtime/execution/__init__.py +13 -0
- crewplane/runtime/execution/activity/__init__.py +8 -0
- crewplane/runtime/execution/activity/console.py +38 -0
- crewplane/runtime/execution/activity/events.py +362 -0
- crewplane/runtime/execution/activity/telemetry.py +65 -0
- crewplane/runtime/execution/common.py +70 -0
- crewplane/runtime/execution/consensus.py +54 -0
- crewplane/runtime/execution/deferred_cleanup.py +137 -0
- crewplane/runtime/execution/fragment_assembler.py +260 -0
- crewplane/runtime/execution/input.py +75 -0
- crewplane/runtime/execution/log_presentation.py +61 -0
- crewplane/runtime/execution/parallel.py +263 -0
- crewplane/runtime/execution/prompt_budgeting.py +192 -0
- crewplane/runtime/execution/provider_call/__init__.py +63 -0
- crewplane/runtime/execution/provider_call/display.py +117 -0
- crewplane/runtime/execution/provider_call/events.py +114 -0
- crewplane/runtime/execution/provider_call/generated_file_changes.py +297 -0
- crewplane/runtime/execution/provider_call/generated_files.py +335 -0
- crewplane/runtime/execution/provider_call/lifecycle.py +470 -0
- crewplane/runtime/execution/provider_call/types.py +46 -0
- crewplane/runtime/execution/provider_call/workspace.py +133 -0
- crewplane/runtime/execution/resume.py +100 -0
- crewplane/runtime/execution/review_loop/__init__.py +481 -0
- crewplane/runtime/execution/review_loop/audit_round.py +357 -0
- crewplane/runtime/execution/review_loop/drift.py +159 -0
- crewplane/runtime/execution/review_loop/drift_detection.py +343 -0
- crewplane/runtime/execution/review_loop/drift_events.py +82 -0
- crewplane/runtime/execution/review_loop/executor_round.py +118 -0
- crewplane/runtime/execution/review_loop/policy.py +96 -0
- crewplane/runtime/execution/review_loop/prompts.py +199 -0
- crewplane/runtime/execution/review_loop/reviewer_round.py +334 -0
- crewplane/runtime/execution/review_loop/rounds.py +11 -0
- crewplane/runtime/execution/review_loop/state.py +314 -0
- crewplane/runtime/execution/review_loop/types.py +379 -0
- crewplane/runtime/execution/review_loop/validation.py +367 -0
- crewplane/runtime/execution/review_loop/workspace_state_paths.py +95 -0
- crewplane/runtime/execution/reviews/__init__.py +1 -0
- crewplane/runtime/execution/reviews/consensus.py +203 -0
- crewplane/runtime/execution/reviews/fingerprints.py +147 -0
- crewplane/runtime/execution/reviews/markdown.py +68 -0
- crewplane/runtime/execution/reviews/plain_language.py +124 -0
- crewplane/runtime/execution/reviews/structured.py +353 -0
- crewplane/runtime/execution/reviews/types.py +50 -0
- crewplane/runtime/execution/runtime_context.py +186 -0
- crewplane/runtime/execution/sequential.py +114 -0
- crewplane/runtime/execution/stage_finalize_events.py +62 -0
- crewplane/runtime/execution/stage_tasks.py +76 -0
- crewplane/runtime/execution/workflow/__init__.py +397 -0
- crewplane/runtime/execution/workflow/cleanup.py +170 -0
- crewplane/runtime/execution/workflow/node.py +128 -0
- crewplane/runtime/execution/workflow/state.py +94 -0
- crewplane/runtime/execution/workspace_files/__init__.py +394 -0
- crewplane/runtime/execution/workspace_files/generated.py +149 -0
- crewplane/runtime/execution/workspace_files/source_resolution.py +155 -0
- crewplane/runtime/workspace/__init__.py +8 -0
- crewplane/runtime/workspace/branch_export/__init__.py +389 -0
- crewplane/runtime/workspace/branch_export/checkpoint.py +246 -0
- crewplane/runtime/workspace/branch_export/fulfillment.py +194 -0
- crewplane/runtime/workspace/branch_export/git.py +141 -0
- crewplane/runtime/workspace/branch_export/records.py +233 -0
- crewplane/runtime/workspace/cleanup.py +254 -0
- crewplane/runtime/workspace/cleanup_notes.py +9 -0
- crewplane/runtime/workspace/git.py +73 -0
- crewplane/runtime/workspace/invocation.py +94 -0
- crewplane/runtime/workspace/locks.py +89 -0
- crewplane/runtime/workspace/materialization.py +42 -0
- crewplane/runtime/workspace/prepared_workspace.py +407 -0
- crewplane/runtime/workspace/service/__init__.py +64 -0
- crewplane/runtime/workspace/service/common.py +165 -0
- crewplane/runtime/workspace/service/snapshot.py +248 -0
- crewplane/runtime/workspace/service/types.py +81 -0
- crewplane/runtime/workspace/service/worktree.py +478 -0
- crewplane/runtime/workspace/service/worktree_failures.py +136 -0
- crewplane/runtime/workspace/setup.py +409 -0
- crewplane/runtime/workspace/snapshot.py +332 -0
- crewplane/runtime/workspace/state.py +365 -0
- crewplane/runtime/workspace/state_selection.py +268 -0
- crewplane/runtime/workspace/worktree/__init__.py +425 -0
- crewplane/runtime/workspace/worktree/attributes.py +33 -0
- crewplane/runtime/workspace/worktree/cache.py +291 -0
- crewplane/runtime/workspace/worktree/cleanup.py +222 -0
- crewplane/runtime/workspace/worktree/commit.py +52 -0
- crewplane/runtime/workspace/worktree/descriptors.py +185 -0
- crewplane/runtime/workspace/worktree/inspection.py +386 -0
- crewplane/runtime/workspace/worktree/lineage.py +434 -0
- crewplane/runtime/workspace/worktree/materialization.py +139 -0
- crewplane/runtime/workspace/worktree/policy.py +107 -0
- crewplane/runtime/workspace/worktree/protected_refs.py +63 -0
- crewplane/runtime/workspace/worktree/ref_cleanup.py +79 -0
- crewplane/runtime/workspace/worktree/refs.py +57 -0
- crewplane/runtime/workspace/worktree/reset.py +234 -0
- crewplane/runtime/workspace/worktree/result_validation.py +44 -0
- crewplane/runtime/workspace/worktree/reuse.py +95 -0
- crewplane/runtime/workspace/worktree/source_refs.py +90 -0
- crewplane/runtime/workspace/worktree/types.py +69 -0
- crewplane/version.py +5 -0
- crewplane-0.1.0.dist-info/METADATA +211 -0
- crewplane-0.1.0.dist-info/RECORD +417 -0
- crewplane-0.1.0.dist-info/WHEEL +4 -0
- crewplane-0.1.0.dist-info/entry_points.txt +2 -0
- crewplane-0.1.0.dist-info/licenses/LICENSE +201 -0
crewplane/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__all__: list[str] = []
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from crewplane.architecture.contracts import (
|
|
6
|
+
CanonicalIntegrationConfig,
|
|
7
|
+
FilesystemArtifactOptions,
|
|
8
|
+
JsonObject,
|
|
9
|
+
)
|
|
10
|
+
from crewplane.architecture.ports.artifacts import (
|
|
11
|
+
ArtifactStorePort,
|
|
12
|
+
)
|
|
13
|
+
from crewplane.artifacts import OutputManager
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _parse_options(options: JsonObject | None) -> FilesystemArtifactOptions:
|
|
17
|
+
resolved_options = dict(options or {})
|
|
18
|
+
|
|
19
|
+
log_cli_output_raw = resolved_options.pop("log_cli_output", True)
|
|
20
|
+
if not isinstance(log_cli_output_raw, bool):
|
|
21
|
+
raise ValueError("artifacts option 'log_cli_output' must be a boolean")
|
|
22
|
+
|
|
23
|
+
allowed_template_paths_raw = resolved_options.pop("allowed_template_paths", [])
|
|
24
|
+
if not isinstance(allowed_template_paths_raw, list) or any(
|
|
25
|
+
not isinstance(path, str) for path in allowed_template_paths_raw
|
|
26
|
+
):
|
|
27
|
+
raise ValueError(
|
|
28
|
+
"artifacts option 'allowed_template_paths' must be a list of strings"
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
if resolved_options:
|
|
32
|
+
raise ValueError(
|
|
33
|
+
"Unsupported filesystem artifacts options: "
|
|
34
|
+
f"{', '.join(sorted(resolved_options))}"
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
return FilesystemArtifactOptions(
|
|
38
|
+
log_cli_output=log_cli_output_raw,
|
|
39
|
+
allowed_template_paths=tuple(
|
|
40
|
+
Path(path).expanduser().resolve(strict=False).as_posix()
|
|
41
|
+
for path in allowed_template_paths_raw
|
|
42
|
+
),
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class FilesystemArtifactsAdapter:
|
|
47
|
+
"""Create the built-in filesystem-backed artifact store."""
|
|
48
|
+
|
|
49
|
+
def canonicalize_options(
|
|
50
|
+
self,
|
|
51
|
+
implementation: str,
|
|
52
|
+
resolved_identity: str,
|
|
53
|
+
options: JsonObject | None = None,
|
|
54
|
+
) -> CanonicalIntegrationConfig:
|
|
55
|
+
parsed_options = _parse_options(options)
|
|
56
|
+
canonical_options = {
|
|
57
|
+
"allowed_template_paths": list(parsed_options.allowed_template_paths),
|
|
58
|
+
"log_cli_output": parsed_options.log_cli_output,
|
|
59
|
+
}
|
|
60
|
+
return CanonicalIntegrationConfig(
|
|
61
|
+
implementation=implementation,
|
|
62
|
+
resolved_identity=resolved_identity,
|
|
63
|
+
options=canonical_options,
|
|
64
|
+
option_scopes={
|
|
65
|
+
"allowed_template_paths": "artifact",
|
|
66
|
+
"log_cli_output": "artifact",
|
|
67
|
+
},
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
def create_store(
|
|
71
|
+
self,
|
|
72
|
+
workflow_name: str,
|
|
73
|
+
state_dir: Path,
|
|
74
|
+
project_root: Path,
|
|
75
|
+
options: JsonObject | None = None,
|
|
76
|
+
) -> ArtifactStorePort:
|
|
77
|
+
"""Build an artifact store rooted under Crewplane directory."""
|
|
78
|
+
|
|
79
|
+
parsed_options = _parse_options(options)
|
|
80
|
+
|
|
81
|
+
return OutputManager(
|
|
82
|
+
workflow_name,
|
|
83
|
+
base_dir=state_dir,
|
|
84
|
+
template_base_dir=project_root,
|
|
85
|
+
log_cli_output=parsed_options.log_cli_output,
|
|
86
|
+
)
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import shutil
|
|
5
|
+
from collections.abc import Callable
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from crewplane.architecture.contracts import (
|
|
9
|
+
AgentInvoker,
|
|
10
|
+
CanonicalIntegrationConfig,
|
|
11
|
+
CliInvokerOptions,
|
|
12
|
+
InvokerAdapterCapabilities,
|
|
13
|
+
JsonObject,
|
|
14
|
+
)
|
|
15
|
+
from crewplane.core.config import Config
|
|
16
|
+
from crewplane.core.workflow.models import WorkflowPlan
|
|
17
|
+
from crewplane.runtime.agent.invoker import PlannedAgentInvoker
|
|
18
|
+
|
|
19
|
+
from .cli_invoker import build_cli_invocation_plan, build_cli_log_presentation
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def collect_cli_availability_errors(
|
|
23
|
+
workflow: WorkflowPlan,
|
|
24
|
+
config: Config,
|
|
25
|
+
which_fn: Callable[[str], str | None] | None = None,
|
|
26
|
+
project_root: Path | None = None,
|
|
27
|
+
) -> list[str]:
|
|
28
|
+
"""Collect missing executable errors for configured CLI providers."""
|
|
29
|
+
|
|
30
|
+
executable_lookup = shutil.which if which_fn is None else which_fn
|
|
31
|
+
executable_base_dir = Path.cwd() if project_root is None else project_root
|
|
32
|
+
missing_cli_locations: dict[str, list[str]] = {}
|
|
33
|
+
for node in workflow.nodes:
|
|
34
|
+
for provider in node.providers:
|
|
35
|
+
agent_config = config.agents.get(provider.provider)
|
|
36
|
+
if agent_config is None:
|
|
37
|
+
continue
|
|
38
|
+
cli_executable = agent_config.cli_cmd[0]
|
|
39
|
+
if _cli_executable_available(
|
|
40
|
+
cli_executable,
|
|
41
|
+
executable_lookup,
|
|
42
|
+
executable_base_dir,
|
|
43
|
+
):
|
|
44
|
+
continue
|
|
45
|
+
location = f"workflow '{workflow.name}' -> node '{node.id}'"
|
|
46
|
+
missing_cli_locations.setdefault(provider.provider, []).append(
|
|
47
|
+
f"{location} (CLI: {cli_executable})"
|
|
48
|
+
)
|
|
49
|
+
return _format_missing_cli_errors(config, missing_cli_locations)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _format_missing_cli_errors(
|
|
53
|
+
config: Config,
|
|
54
|
+
missing_cli_locations: dict[str, list[str]],
|
|
55
|
+
) -> list[str]:
|
|
56
|
+
errors: list[str] = []
|
|
57
|
+
for provider_name, locations in sorted(missing_cli_locations.items()):
|
|
58
|
+
unique_locations = sorted(set(locations))
|
|
59
|
+
cli_executable = config.agents[provider_name].cli_cmd[0]
|
|
60
|
+
availability_message = (
|
|
61
|
+
"not found or not executable"
|
|
62
|
+
if Path(cli_executable).is_absolute()
|
|
63
|
+
or _contains_path_separator(cli_executable)
|
|
64
|
+
else "not found in PATH"
|
|
65
|
+
)
|
|
66
|
+
errors.append(
|
|
67
|
+
f"CLI '{cli_executable}' {availability_message} for provider "
|
|
68
|
+
f"'{provider_name}', referenced in: {', '.join(unique_locations)}"
|
|
69
|
+
)
|
|
70
|
+
return errors
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _cli_executable_available(
|
|
74
|
+
executable: str,
|
|
75
|
+
executable_lookup: Callable[[str], str | None],
|
|
76
|
+
executable_base_dir: Path,
|
|
77
|
+
) -> bool:
|
|
78
|
+
executable_path = Path(executable)
|
|
79
|
+
if executable_path.is_absolute():
|
|
80
|
+
return _is_executable_file(executable_path)
|
|
81
|
+
if _contains_path_separator(executable):
|
|
82
|
+
return _is_executable_file(executable_base_dir / executable_path)
|
|
83
|
+
return executable_lookup(executable) is not None
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _is_executable_file(path: Path) -> bool:
|
|
87
|
+
resolved = path.resolve(strict=False)
|
|
88
|
+
return resolved.is_file() and os.access(resolved, os.X_OK)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _contains_path_separator(value: str) -> bool:
|
|
92
|
+
return "/" in value or "\\" in value
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class CliInvokerAdapter:
|
|
96
|
+
"""Create the default CLI-backed agent invoker."""
|
|
97
|
+
|
|
98
|
+
def workspace_capabilities(self) -> InvokerAdapterCapabilities:
|
|
99
|
+
return InvokerAdapterCapabilities.workspace_supported(
|
|
100
|
+
launch_mode="runtime_command_runner",
|
|
101
|
+
controlled_child_environment=True,
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
def canonicalize_options(
|
|
105
|
+
self,
|
|
106
|
+
implementation: str,
|
|
107
|
+
resolved_identity: str,
|
|
108
|
+
options: JsonObject | None = None,
|
|
109
|
+
) -> CanonicalIntegrationConfig:
|
|
110
|
+
CliInvokerOptions()
|
|
111
|
+
if options:
|
|
112
|
+
raise ValueError(
|
|
113
|
+
"cli invoker implementation does not support options; "
|
|
114
|
+
"set settings.integrations.invoker.options: {} when using "
|
|
115
|
+
f'implementation: "cli"; got: {sorted(options)}'
|
|
116
|
+
)
|
|
117
|
+
return CanonicalIntegrationConfig(
|
|
118
|
+
implementation=implementation,
|
|
119
|
+
resolved_identity=resolved_identity,
|
|
120
|
+
options={},
|
|
121
|
+
option_scopes={},
|
|
122
|
+
capabilities=self.workspace_capabilities().as_dict(),
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
def create_invoker(
|
|
126
|
+
self,
|
|
127
|
+
config: Config,
|
|
128
|
+
options: JsonObject | None = None,
|
|
129
|
+
) -> AgentInvoker:
|
|
130
|
+
"""Build the default subprocess-based invoker."""
|
|
131
|
+
|
|
132
|
+
_validate_config(config)
|
|
133
|
+
self.canonicalize_options("cli", self.__class__.__module__, options)
|
|
134
|
+
return PlannedAgentInvoker(
|
|
135
|
+
plan_builder=build_cli_invocation_plan,
|
|
136
|
+
log_presentation_builder=build_cli_log_presentation,
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _validate_config(config: Config) -> None:
|
|
141
|
+
if not isinstance(config, Config):
|
|
142
|
+
raise TypeError("config must be a Config instance")
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from .capabilities import (
|
|
2
|
+
CliProviderCapability,
|
|
3
|
+
build_cli_invocation_plan,
|
|
4
|
+
build_cli_log_presentation,
|
|
5
|
+
get_cli_provider_capability,
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"CliProviderCapability",
|
|
10
|
+
"build_cli_invocation_plan",
|
|
11
|
+
"build_cli_log_presentation",
|
|
12
|
+
"get_cli_provider_capability",
|
|
13
|
+
]
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import shutil
|
|
5
|
+
import tempfile
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from datetime import UTC, datetime
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
from crewplane.architecture.contracts import (
|
|
11
|
+
InvocationPlan,
|
|
12
|
+
LogPresentationDescriptor,
|
|
13
|
+
LogPresentationFormat,
|
|
14
|
+
OutputExtractionMode,
|
|
15
|
+
ProviderKind,
|
|
16
|
+
QuotaParserProfile,
|
|
17
|
+
StructuredOutputMode,
|
|
18
|
+
UsageParserProfile,
|
|
19
|
+
)
|
|
20
|
+
from crewplane.core.config import AgentConfig
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass(frozen=True)
|
|
24
|
+
class CliProviderCapability:
|
|
25
|
+
provider_kind: ProviderKind
|
|
26
|
+
structured_output_mode: StructuredOutputMode
|
|
27
|
+
output_extraction_mode: OutputExtractionMode
|
|
28
|
+
quota_parser: QuotaParserProfile
|
|
29
|
+
usage_parser: UsageParserProfile
|
|
30
|
+
log_presentation_format: LogPresentationFormat
|
|
31
|
+
log_presentation_profile: str
|
|
32
|
+
model_arg: str | None = "--model"
|
|
33
|
+
structured_output_args: tuple[str, ...] = ()
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
CAPABILITIES: dict[ProviderKind, CliProviderCapability] = {
|
|
37
|
+
ProviderKind.CLAUDE: CliProviderCapability(
|
|
38
|
+
provider_kind=ProviderKind.CLAUDE,
|
|
39
|
+
structured_output_mode="claude_json",
|
|
40
|
+
output_extraction_mode="claude_json",
|
|
41
|
+
quota_parser="claude",
|
|
42
|
+
usage_parser="claude",
|
|
43
|
+
log_presentation_format="json_object",
|
|
44
|
+
log_presentation_profile="claude",
|
|
45
|
+
structured_output_args=("--output-format", "json"),
|
|
46
|
+
),
|
|
47
|
+
ProviderKind.CODEX: CliProviderCapability(
|
|
48
|
+
provider_kind=ProviderKind.CODEX,
|
|
49
|
+
structured_output_mode="codex_last_message_file",
|
|
50
|
+
output_extraction_mode="codex_last_message_file",
|
|
51
|
+
quota_parser="codex",
|
|
52
|
+
usage_parser="codex",
|
|
53
|
+
log_presentation_format="json_lines",
|
|
54
|
+
log_presentation_profile="codex",
|
|
55
|
+
),
|
|
56
|
+
ProviderKind.COPILOT: CliProviderCapability(
|
|
57
|
+
provider_kind=ProviderKind.COPILOT,
|
|
58
|
+
structured_output_mode="none",
|
|
59
|
+
output_extraction_mode="visible",
|
|
60
|
+
quota_parser="copilot",
|
|
61
|
+
usage_parser="none",
|
|
62
|
+
log_presentation_format="plain",
|
|
63
|
+
log_presentation_profile="generic",
|
|
64
|
+
),
|
|
65
|
+
ProviderKind.GEMINI: CliProviderCapability(
|
|
66
|
+
provider_kind=ProviderKind.GEMINI,
|
|
67
|
+
structured_output_mode="none",
|
|
68
|
+
output_extraction_mode="visible",
|
|
69
|
+
quota_parser="gemini",
|
|
70
|
+
usage_parser="none",
|
|
71
|
+
log_presentation_format="plain",
|
|
72
|
+
log_presentation_profile="generic",
|
|
73
|
+
),
|
|
74
|
+
ProviderKind.KILO: CliProviderCapability(
|
|
75
|
+
provider_kind=ProviderKind.KILO,
|
|
76
|
+
structured_output_mode="none",
|
|
77
|
+
output_extraction_mode="visible",
|
|
78
|
+
quota_parser="kilo",
|
|
79
|
+
usage_parser="none",
|
|
80
|
+
log_presentation_format="plain",
|
|
81
|
+
log_presentation_profile="generic",
|
|
82
|
+
),
|
|
83
|
+
ProviderKind.GENERIC: CliProviderCapability(
|
|
84
|
+
provider_kind=ProviderKind.GENERIC,
|
|
85
|
+
structured_output_mode="none",
|
|
86
|
+
output_extraction_mode="visible",
|
|
87
|
+
quota_parser="generic",
|
|
88
|
+
usage_parser="none",
|
|
89
|
+
log_presentation_format="plain",
|
|
90
|
+
log_presentation_profile="generic",
|
|
91
|
+
),
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def get_cli_provider_capability(provider_kind: ProviderKind) -> CliProviderCapability:
|
|
96
|
+
return CAPABILITIES[ProviderKind(provider_kind)]
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def build_cli_invocation_plan(
|
|
100
|
+
config: AgentConfig,
|
|
101
|
+
model: str | None,
|
|
102
|
+
prompt: str,
|
|
103
|
+
output_file: Path,
|
|
104
|
+
) -> InvocationPlan:
|
|
105
|
+
capability = get_cli_provider_capability(config.provider_kind)
|
|
106
|
+
structured_output_file = _structured_output_file(capability)
|
|
107
|
+
cmd = _build_argv(config, capability, model, prompt, structured_output_file)
|
|
108
|
+
stdin_data = prompt.encode("utf-8") if config.prompt_transport == "stdin" else None
|
|
109
|
+
return InvocationPlan(
|
|
110
|
+
cmd=cmd,
|
|
111
|
+
stdin_data=stdin_data,
|
|
112
|
+
structured_output_file=structured_output_file,
|
|
113
|
+
structured_output_mode=capability.structured_output_mode,
|
|
114
|
+
output_extraction_mode=capability.output_extraction_mode,
|
|
115
|
+
quota_parser=capability.quota_parser,
|
|
116
|
+
usage_parser=capability.usage_parser,
|
|
117
|
+
failure_profile=capability.provider_kind,
|
|
118
|
+
log_provider_kind=capability.provider_kind,
|
|
119
|
+
log_header=_build_log_header(
|
|
120
|
+
cli_executable=cmd[0],
|
|
121
|
+
model=model,
|
|
122
|
+
output_file=output_file,
|
|
123
|
+
),
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def build_cli_log_presentation(config: AgentConfig) -> LogPresentationDescriptor:
|
|
128
|
+
capability = get_cli_provider_capability(config.provider_kind)
|
|
129
|
+
return LogPresentationDescriptor(
|
|
130
|
+
format=capability.log_presentation_format,
|
|
131
|
+
profile=capability.log_presentation_profile,
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _build_argv(
|
|
136
|
+
config: AgentConfig,
|
|
137
|
+
capability: CliProviderCapability,
|
|
138
|
+
model: str | None,
|
|
139
|
+
prompt: str,
|
|
140
|
+
structured_output_file: Path | None,
|
|
141
|
+
) -> list[str]:
|
|
142
|
+
"""Build the provider CLI argv and validate the executable.
|
|
143
|
+
|
|
144
|
+
The first argument identifies the process to launch, so it is resolved
|
|
145
|
+
before appending provider flags. This keeps command validation and log
|
|
146
|
+
headers tied to the actual executable while leaving user-supplied
|
|
147
|
+
arguments untouched.
|
|
148
|
+
"""
|
|
149
|
+
cmd = config.get_command()
|
|
150
|
+
cmd[0] = _resolved_cli_executable(cmd[0])
|
|
151
|
+
model_arg = (
|
|
152
|
+
config.model_arg
|
|
153
|
+
if config.provider_kind == ProviderKind.GENERIC
|
|
154
|
+
else capability.model_arg
|
|
155
|
+
)
|
|
156
|
+
if model_arg is not None and model is not None:
|
|
157
|
+
cmd.extend([model_arg, model])
|
|
158
|
+
cmd.extend(config.extra_args)
|
|
159
|
+
cmd.extend(_structured_output_args(capability, structured_output_file))
|
|
160
|
+
if config.prompt_transport == "stdin":
|
|
161
|
+
if config.prompt_transport_arg:
|
|
162
|
+
cmd.append(config.prompt_transport_arg)
|
|
163
|
+
return cmd
|
|
164
|
+
if config.prompt_transport_arg is None:
|
|
165
|
+
raise ValueError("prompt_transport_arg is required for argv prompt transport.")
|
|
166
|
+
cmd.extend([config.prompt_transport_arg, prompt])
|
|
167
|
+
return cmd
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def _resolved_cli_executable(executable: str) -> str:
|
|
171
|
+
"""Return an executable path suitable for subprocess invocation.
|
|
172
|
+
|
|
173
|
+
Bare executable names are resolved through `PATH` and validated. Absolute
|
|
174
|
+
paths are validated directly. Relative path-like commands are preserved so
|
|
175
|
+
subprocess can resolve them relative to the configured working directory.
|
|
176
|
+
"""
|
|
177
|
+
executable_path = Path(executable)
|
|
178
|
+
if executable_path.is_absolute():
|
|
179
|
+
return _resolved_existing_executable(executable_path)
|
|
180
|
+
if _contains_path_separator(executable):
|
|
181
|
+
return executable
|
|
182
|
+
resolved = shutil.which(executable)
|
|
183
|
+
if resolved is None:
|
|
184
|
+
raise FileNotFoundError(f"CLI executable '{executable}' was not found.")
|
|
185
|
+
return _resolved_existing_executable(Path(resolved))
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def _resolved_existing_executable(executable: Path) -> str:
|
|
189
|
+
"""Resolve an existing executable path and fail clearly if it is unusable."""
|
|
190
|
+
resolved = executable.resolve(strict=True)
|
|
191
|
+
if not resolved.is_file():
|
|
192
|
+
raise FileNotFoundError(
|
|
193
|
+
f"CLI executable '{executable.as_posix()}' is not a file."
|
|
194
|
+
)
|
|
195
|
+
if not os.access(resolved, os.X_OK):
|
|
196
|
+
raise PermissionError(
|
|
197
|
+
f"CLI executable '{resolved.as_posix()}' is not executable."
|
|
198
|
+
)
|
|
199
|
+
return resolved.as_posix()
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def _contains_path_separator(value: str) -> bool:
|
|
203
|
+
return "/" in value or "\\" in value
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def _structured_output_args(
|
|
207
|
+
capability: CliProviderCapability,
|
|
208
|
+
structured_output_file: Path | None,
|
|
209
|
+
) -> tuple[str, ...]:
|
|
210
|
+
if capability.structured_output_mode == "codex_last_message_file":
|
|
211
|
+
if structured_output_file is None:
|
|
212
|
+
raise ValueError("Codex invocations require a structured output file.")
|
|
213
|
+
return ("--json", "--output-last-message", str(structured_output_file))
|
|
214
|
+
return capability.structured_output_args
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def _structured_output_file(capability: CliProviderCapability) -> Path | None:
|
|
218
|
+
if capability.structured_output_mode != "codex_last_message_file":
|
|
219
|
+
return None
|
|
220
|
+
file_descriptor, temp_path = tempfile.mkstemp(
|
|
221
|
+
prefix="crewplane-codex-",
|
|
222
|
+
suffix=".last-message.txt",
|
|
223
|
+
)
|
|
224
|
+
os.close(file_descriptor)
|
|
225
|
+
Path(temp_path).unlink(missing_ok=True)
|
|
226
|
+
return Path(temp_path)
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def _build_log_header(
|
|
230
|
+
cli_executable: str,
|
|
231
|
+
model: str | None,
|
|
232
|
+
output_file: Path,
|
|
233
|
+
) -> bytes:
|
|
234
|
+
started_at = datetime.now(UTC).isoformat()
|
|
235
|
+
model_label = model if model is not None else "provider default"
|
|
236
|
+
header = (
|
|
237
|
+
f"started_at: {started_at}\n"
|
|
238
|
+
f"cli_executable: {cli_executable}\n"
|
|
239
|
+
f"model: {model_label}\n"
|
|
240
|
+
f"output_file: {output_file}\n"
|
|
241
|
+
"---\n"
|
|
242
|
+
)
|
|
243
|
+
return header.encode("utf-8")
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
__all__ = ["MockInvokerAdapter"]
|
|
4
|
+
|
|
5
|
+
from crewplane.architecture.contracts import (
|
|
6
|
+
AgentInvoker,
|
|
7
|
+
CanonicalIntegrationConfig,
|
|
8
|
+
InvokerAdapterCapabilities,
|
|
9
|
+
JsonObject,
|
|
10
|
+
)
|
|
11
|
+
from crewplane.core.config import Config
|
|
12
|
+
|
|
13
|
+
from .mock_invoker import MockAgentInvoker, parse_options
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class MockInvokerAdapter:
|
|
17
|
+
"""Create deterministic mock invokers for local orchestration runs."""
|
|
18
|
+
|
|
19
|
+
def workspace_capabilities(self) -> InvokerAdapterCapabilities:
|
|
20
|
+
return InvokerAdapterCapabilities.workspace_supported(
|
|
21
|
+
launch_mode="mock_no_child_process",
|
|
22
|
+
controlled_child_environment=False,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
def canonicalize_options(
|
|
26
|
+
self,
|
|
27
|
+
implementation: str,
|
|
28
|
+
resolved_identity: str,
|
|
29
|
+
options: JsonObject | None = None,
|
|
30
|
+
) -> CanonicalIntegrationConfig:
|
|
31
|
+
parsed = parse_options(options)
|
|
32
|
+
canonical_options = {
|
|
33
|
+
"delay_seconds": parsed.delay_seconds,
|
|
34
|
+
"fail_when": [selector.__dict__ for selector in parsed.fail_when],
|
|
35
|
+
"observation_delay_seconds": parsed.observation_delay_seconds,
|
|
36
|
+
"output_dir": parsed.output_dir,
|
|
37
|
+
"output_mode": parsed.output_mode,
|
|
38
|
+
"seed": parsed.seed,
|
|
39
|
+
"strict_file_mode": parsed.strict_file_mode,
|
|
40
|
+
}
|
|
41
|
+
return CanonicalIntegrationConfig(
|
|
42
|
+
implementation=implementation,
|
|
43
|
+
resolved_identity=resolved_identity,
|
|
44
|
+
options=canonical_options,
|
|
45
|
+
option_scopes={key: "execution" for key in canonical_options},
|
|
46
|
+
capabilities=self.workspace_capabilities().as_dict(),
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
def create_invoker(
|
|
50
|
+
self,
|
|
51
|
+
config: Config,
|
|
52
|
+
options: JsonObject | None = None,
|
|
53
|
+
) -> AgentInvoker:
|
|
54
|
+
"""Build a mock invoker from the configured integration options."""
|
|
55
|
+
|
|
56
|
+
_validate_config(config)
|
|
57
|
+
return MockAgentInvoker(options=parse_options(options))
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _validate_config(config: Config) -> None:
|
|
61
|
+
if not isinstance(config, Config):
|
|
62
|
+
raise TypeError("config must be a Config instance")
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
from crewplane.architecture.contracts import InvocationContext
|
|
6
|
+
from crewplane.core.workflow.keywords import ProviderRole
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass(frozen=True)
|
|
10
|
+
class ContextDisplay:
|
|
11
|
+
node_id: str
|
|
12
|
+
task_id: str
|
|
13
|
+
provider: str
|
|
14
|
+
role: str
|
|
15
|
+
audit_round_display: str
|
|
16
|
+
round_display: str
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _or_na(value: int | None) -> str:
|
|
20
|
+
return str(value) if value is not None else "n/a"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def context_display(context: InvocationContext | None) -> ContextDisplay:
|
|
24
|
+
if context is None:
|
|
25
|
+
return ContextDisplay(
|
|
26
|
+
node_id="<unknown>",
|
|
27
|
+
task_id="<unknown>",
|
|
28
|
+
provider="<unknown>",
|
|
29
|
+
role="<unknown>",
|
|
30
|
+
audit_round_display=_or_na(None),
|
|
31
|
+
round_display=_or_na(None),
|
|
32
|
+
)
|
|
33
|
+
return ContextDisplay(
|
|
34
|
+
node_id=context.node_id,
|
|
35
|
+
task_id=context.task_id,
|
|
36
|
+
provider=context.provider,
|
|
37
|
+
role=context.role,
|
|
38
|
+
audit_round_display=_or_na(context.audit_round_num),
|
|
39
|
+
round_display=_or_na(context.round_num),
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def is_reviewer_context(context: InvocationContext | None) -> bool:
|
|
44
|
+
return context is not None and context.role == ProviderRole.REVIEWER
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from crewplane.architecture.contracts import InvocationContext
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def fixture_candidates(
|
|
9
|
+
output_dir: Path, context: InvocationContext | None
|
|
10
|
+
) -> tuple[Path, ...]:
|
|
11
|
+
"""Return candidate fixture paths from most specific to least specific."""
|
|
12
|
+
|
|
13
|
+
candidates: list[Path] = []
|
|
14
|
+
if context is not None:
|
|
15
|
+
node_dir = output_dir / context.node_id
|
|
16
|
+
role_name = context.role.strip().lower()
|
|
17
|
+
grouped_node_dir = (
|
|
18
|
+
node_dir / f"review-audit-round-{context.audit_round_num}"
|
|
19
|
+
if context.audit_round_num is not None
|
|
20
|
+
else None
|
|
21
|
+
)
|
|
22
|
+
search_dirs = [
|
|
23
|
+
candidate
|
|
24
|
+
for candidate in (grouped_node_dir, node_dir)
|
|
25
|
+
if candidate is not None
|
|
26
|
+
]
|
|
27
|
+
for search_dir in search_dirs:
|
|
28
|
+
if context.round_num is not None:
|
|
29
|
+
candidates.append(
|
|
30
|
+
search_dir / f"{context.task_id}_round{context.round_num}.md"
|
|
31
|
+
)
|
|
32
|
+
candidates.append(
|
|
33
|
+
search_dir / f"{role_name}-round-{context.round_num}.md"
|
|
34
|
+
)
|
|
35
|
+
candidates.append(search_dir / f"{context.task_id}.md")
|
|
36
|
+
candidates.append(search_dir / f"{role_name}.md")
|
|
37
|
+
if search_dir == grouped_node_dir:
|
|
38
|
+
candidates.append(search_dir / f"default-{role_name}.md")
|
|
39
|
+
candidates.append(output_dir / f"{context.node_id}.md")
|
|
40
|
+
candidates.append(output_dir / f"default-{role_name}.md")
|
|
41
|
+
candidates.append(output_dir / "default.md")
|
|
42
|
+
return tuple(candidates)
|