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.
Files changed (417) hide show
  1. crewplane/__init__.py +3 -0
  2. crewplane/adapters/__init__.py +1 -0
  3. crewplane/adapters/artifacts/__init__.py +3 -0
  4. crewplane/adapters/artifacts/filesystem.py +86 -0
  5. crewplane/adapters/invokers/__init__.py +4 -0
  6. crewplane/adapters/invokers/cli.py +142 -0
  7. crewplane/adapters/invokers/cli_invoker/__init__.py +13 -0
  8. crewplane/adapters/invokers/cli_invoker/capabilities.py +243 -0
  9. crewplane/adapters/invokers/mock.py +62 -0
  10. crewplane/adapters/invokers/mock_invoker/__init__.py +4 -0
  11. crewplane/adapters/invokers/mock_invoker/context.py +44 -0
  12. crewplane/adapters/invokers/mock_invoker/fixtures.py +42 -0
  13. crewplane/adapters/invokers/mock_invoker/invoker.py +236 -0
  14. crewplane/adapters/invokers/mock_invoker/logging.py +80 -0
  15. crewplane/adapters/invokers/mock_invoker/mutations.py +201 -0
  16. crewplane/adapters/invokers/mock_invoker/options.py +142 -0
  17. crewplane/adapters/invokers/mock_invoker/outputs.py +39 -0
  18. crewplane/adapters/invokers/mock_invoker/selectors.py +86 -0
  19. crewplane/adapters/ui/__init__.py +4 -0
  20. crewplane/adapters/ui/null.py +86 -0
  21. crewplane/adapters/ui/tmux.py +158 -0
  22. crewplane/architecture/__init__.py +13 -0
  23. crewplane/architecture/contracts/__init__.py +130 -0
  24. crewplane/architecture/contracts/integration.py +99 -0
  25. crewplane/architecture/contracts/integration_options.py +51 -0
  26. crewplane/architecture/contracts/invocation.py +438 -0
  27. crewplane/architecture/contracts/json.py +5 -0
  28. crewplane/architecture/contracts/observer.py +49 -0
  29. crewplane/architecture/errors.py +17 -0
  30. crewplane/architecture/loader.py +212 -0
  31. crewplane/architecture/ports/__init__.py +14 -0
  32. crewplane/architecture/ports/artifacts.py +161 -0
  33. crewplane/architecture/ports/invoker.py +29 -0
  34. crewplane/architecture/ports/runtime.py +41 -0
  35. crewplane/architecture/ports/ui.py +49 -0
  36. crewplane/architecture/registry.py +35 -0
  37. crewplane/artifacts/__init__.py +23 -0
  38. crewplane/artifacts/atomic.py +93 -0
  39. crewplane/artifacts/directory_manager.py +151 -0
  40. crewplane/artifacts/failure_artifacts.py +40 -0
  41. crewplane/artifacts/generated_files/__init__.py +1 -0
  42. crewplane/artifacts/generated_files/catalog.py +426 -0
  43. crewplane/artifacts/generated_files/detection.py +295 -0
  44. crewplane/artifacts/locks/__init__.py +329 -0
  45. crewplane/artifacts/locks/manifest.py +178 -0
  46. crewplane/artifacts/locks/process_identity.py +57 -0
  47. crewplane/artifacts/manager.py +233 -0
  48. crewplane/artifacts/naming.py +139 -0
  49. crewplane/artifacts/results/__init__.py +1 -0
  50. crewplane/artifacts/results/aggregation.py +136 -0
  51. crewplane/artifacts/results/findings.py +95 -0
  52. crewplane/artifacts/results/review_loop_status.py +207 -0
  53. crewplane/artifacts/results/selection.py +86 -0
  54. crewplane/artifacts/results/stage_document.py +80 -0
  55. crewplane/artifacts/results/stage_outputs.py +16 -0
  56. crewplane/artifacts/results/writer.py +169 -0
  57. crewplane/artifacts/resume/__init__.py +1 -0
  58. crewplane/artifacts/resume/decision.py +39 -0
  59. crewplane/artifacts/resume/generated_files.py +71 -0
  60. crewplane/artifacts/resume/hydration.py +474 -0
  61. crewplane/artifacts/resume/validation.py +349 -0
  62. crewplane/artifacts/run_history.py +218 -0
  63. crewplane/artifacts/safe_files.py +59 -0
  64. crewplane/artifacts/workspace/__init__.py +1 -0
  65. crewplane/artifacts/workspace/bundle_validation.py +450 -0
  66. crewplane/artifacts/workspace/git_blob_hash.py +98 -0
  67. crewplane/artifacts/workspace/node_state.py +332 -0
  68. crewplane/artifacts/workspace/rendered_file_validation.py +302 -0
  69. crewplane/artifacts/workspace/source_validation.py +399 -0
  70. crewplane/artifacts/workspace/state/__init__.py +5 -0
  71. crewplane/artifacts/workspace/state/expected_set.py +172 -0
  72. crewplane/artifacts/workspace/state/fields.py +50 -0
  73. crewplane/artifacts/workspace/state/invocations.py +282 -0
  74. crewplane/artifacts/workspace/state/validation.py +497 -0
  75. crewplane/bootstrap/__init__.py +11 -0
  76. crewplane/bootstrap/container.py +113 -0
  77. crewplane/bootstrap/runtime_config.py +136 -0
  78. crewplane/cli/__init__.py +3 -0
  79. crewplane/cli/app.py +422 -0
  80. crewplane/cli/cleanup.py +300 -0
  81. crewplane/cli/dry_run.py +182 -0
  82. crewplane/cli/paths.py +104 -0
  83. crewplane/cli/run/__init__.py +1 -0
  84. crewplane/cli/run/best_effort_thread.py +38 -0
  85. crewplane/cli/run/branch_export_output.py +125 -0
  86. crewplane/cli/run/components.py +65 -0
  87. crewplane/cli/run/context.py +62 -0
  88. crewplane/cli/run/execution.py +298 -0
  89. crewplane/cli/run/execution_helpers.py +175 -0
  90. crewplane/cli/run/historical_summary.py +360 -0
  91. crewplane/cli/run/manifest.py +95 -0
  92. crewplane/cli/run/observability.py +385 -0
  93. crewplane/cli/run/preflight.py +378 -0
  94. crewplane/cli/run/preflight_success.py +89 -0
  95. crewplane/cli/run/resume.py +300 -0
  96. crewplane/cli/run/topology.py +51 -0
  97. crewplane/cli/run/workspace/__init__.py +1 -0
  98. crewplane/cli/run/workspace/cache_policy.py +67 -0
  99. crewplane/cli/run/workspace/disk_policy.py +192 -0
  100. crewplane/cli/run/workspace/filesystem_policy.py +97 -0
  101. crewplane/cli/run/workspace/git_attributes.py +150 -0
  102. crewplane/cli/run/workspace/git_index.py +162 -0
  103. crewplane/cli/run/workspace/git_source.py +330 -0
  104. crewplane/cli/run/workspace/preflight_diagnostics.py +57 -0
  105. crewplane/cli/run/workspace/repo_policy.py +420 -0
  106. crewplane/cli/run/workspace/source_policy.py +322 -0
  107. crewplane/cli/run/workspace/source_types.py +28 -0
  108. crewplane/cli/templates.py +75 -0
  109. crewplane/cli/workflow_runner.py +23 -0
  110. crewplane/core/__init__.py +5 -0
  111. crewplane/core/config.py +327 -0
  112. crewplane/core/execution_state.py +194 -0
  113. crewplane/core/file_hashing.py +18 -0
  114. crewplane/core/platform.py +12 -0
  115. crewplane/core/preflight/__init__.py +67 -0
  116. crewplane/core/preflight/compile_state.py +259 -0
  117. crewplane/core/preflight/compiler.py +385 -0
  118. crewplane/core/preflight/dependency_edges.py +56 -0
  119. crewplane/core/preflight/diagnostics.py +62 -0
  120. crewplane/core/preflight/execution_nodes.py +183 -0
  121. crewplane/core/preflight/fragment_handlers.py +408 -0
  122. crewplane/core/preflight/input_sources.py +218 -0
  123. crewplane/core/preflight/models.py +483 -0
  124. crewplane/core/preflight/plan_contract.py +160 -0
  125. crewplane/core/preflight/plan_signatures.py +215 -0
  126. crewplane/core/preflight/prompt_transport_warnings.py +38 -0
  127. crewplane/core/preflight/references.py +76 -0
  128. crewplane/core/preflight/render_plans.py +382 -0
  129. crewplane/core/preflight/runner.py +17 -0
  130. crewplane/core/preflight/runtime_config/__init__.py +403 -0
  131. crewplane/core/preflight/runtime_config/redaction.py +314 -0
  132. crewplane/core/preflight/secrets.py +206 -0
  133. crewplane/core/preflight/serialization.py +48 -0
  134. crewplane/core/preflight/signatures.py +11 -0
  135. crewplane/core/preflight/source.py +156 -0
  136. crewplane/core/preflight/static_resources.py +179 -0
  137. crewplane/core/preflight/token_catalog.py +48 -0
  138. crewplane/core/preflight/validation.py +123 -0
  139. crewplane/core/preflight/value_fingerprints.py +79 -0
  140. crewplane/core/preflight/variables.py +7 -0
  141. crewplane/core/preflight/workspace/__init__.py +1 -0
  142. crewplane/core/preflight/workspace/files/__init__.py +1 -0
  143. crewplane/core/preflight/workspace/files/git_reads.py +122 -0
  144. crewplane/core/preflight/workspace/files/locators.py +412 -0
  145. crewplane/core/preflight/workspace/files/paths.py +71 -0
  146. crewplane/core/preflight/workspace/files/selection.py +75 -0
  147. crewplane/core/preflight/workspace/models.py +96 -0
  148. crewplane/core/preflight/workspace/observability.py +224 -0
  149. crewplane/core/preflight/workspace/records.py +63 -0
  150. crewplane/core/preflight/workspace/snapshot_guard.py +44 -0
  151. crewplane/core/prompt_segments.py +55 -0
  152. crewplane/core/provider_names.py +12 -0
  153. crewplane/core/review_contract.py +62 -0
  154. crewplane/core/state_paths.py +31 -0
  155. crewplane/core/token_budget.py +94 -0
  156. crewplane/core/value_checks.py +11 -0
  157. crewplane/core/workflow/__init__.py +1 -0
  158. crewplane/core/workflow/composition/__init__.py +14 -0
  159. crewplane/core/workflow/composition/imports.py +79 -0
  160. crewplane/core/workflow/composition/models.py +85 -0
  161. crewplane/core/workflow/composition/nodes.py +117 -0
  162. crewplane/core/workflow/composition/parsing.py +86 -0
  163. crewplane/core/workflow/composition/rewrites.py +102 -0
  164. crewplane/core/workflow/composition/traversal.py +374 -0
  165. crewplane/core/workflow/diagnostics.py +38 -0
  166. crewplane/core/workflow/graph.py +75 -0
  167. crewplane/core/workflow/keywords.py +59 -0
  168. crewplane/core/workflow/loading.py +112 -0
  169. crewplane/core/workflow/markdown/__init__.py +79 -0
  170. crewplane/core/workflow/markdown/frontmatter.py +68 -0
  171. crewplane/core/workflow/markdown/markers.py +187 -0
  172. crewplane/core/workflow/markdown/models.py +284 -0
  173. crewplane/core/workflow/markdown/payloads.py +102 -0
  174. crewplane/core/workflow/markdown/sections.py +124 -0
  175. crewplane/core/workflow/models.py +340 -0
  176. crewplane/core/workflow/syntax.py +14 -0
  177. crewplane/core/workflow/validation/__init__.py +35 -0
  178. crewplane/core/workflow/validation/api.py +145 -0
  179. crewplane/core/workflow/validation/modes.py +402 -0
  180. crewplane/core/workflow/validation/nodes.py +156 -0
  181. crewplane/core/workflow/validation/policies.py +111 -0
  182. crewplane/core/workflow/validation/templates.py +219 -0
  183. crewplane/core/workflow/validation/workspace.py +229 -0
  184. crewplane/core/workflow/validation/workspace_diagnostics.py +322 -0
  185. crewplane/core/workspace/__init__.py +1 -0
  186. crewplane/core/workspace/cache.py +14 -0
  187. crewplane/core/workspace/git_policy.py +222 -0
  188. crewplane/core/workspace/policy.py +212 -0
  189. crewplane/core/workspace/selection.py +29 -0
  190. crewplane/core/workspace/settings.py +170 -0
  191. crewplane/core/yaml_loader.py +60 -0
  192. crewplane/example_templates/config.yml +213 -0
  193. crewplane/example_templates/example-templates/code-review-example.task.md +80 -0
  194. crewplane/example_templates/example-templates/composition/review-findings-producer-example.task.md +19 -0
  195. crewplane/example_templates/example-templates/composition/review-fix-composed-example.task.md +38 -0
  196. crewplane/example_templates/example-templates/composition/review-fix-consumer-example.task.md +49 -0
  197. crewplane/example_templates/example-templates/design-review-example.task.md +70 -0
  198. crewplane/example_templates/example-templates/feature-implement-example.task.md +95 -0
  199. crewplane/example_templates/example-templates/multi-executor-review-chain-example.task.md +74 -0
  200. crewplane/example_templates/example-templates/refactoring-example.task.md +93 -0
  201. crewplane/example_templates/example-templates/sample-inputs/coding-standards.md +4 -0
  202. crewplane/example_templates/example-templates/sample-inputs/feature-brief.md +4 -0
  203. crewplane/example_templates/example-templates/sample-inputs/review-findings.md +9 -0
  204. crewplane/example_templates/example-templates/test-generation-example.task.md +87 -0
  205. crewplane/example_templates/example-templates/worktree/workspace-alternatives-example.task.md +49 -0
  206. crewplane/example_templates/example-templates/worktree/workspace-inherited-worktree-example.task.md +42 -0
  207. crewplane/example_templates/single-agent-review.task.md +27 -0
  208. crewplane/observability/__init__.py +66 -0
  209. crewplane/observability/dag_graph.py +450 -0
  210. crewplane/observability/dag_render.py +141 -0
  211. crewplane/observability/events/__init__.py +81 -0
  212. crewplane/observability/events/builders.py +215 -0
  213. crewplane/observability/events/dashboard_state.py +129 -0
  214. crewplane/observability/events/execution_event.py +80 -0
  215. crewplane/observability/events/log.py +28 -0
  216. crewplane/observability/events/payloads.py +199 -0
  217. crewplane/observability/events/reducer.py +214 -0
  218. crewplane/observability/events/types.py +40 -0
  219. crewplane/observability/layout.py +188 -0
  220. crewplane/observability/log_presentation/__init__.py +26 -0
  221. crewplane/observability/log_presentation/follow.py +105 -0
  222. crewplane/observability/log_presentation/formatters.py +380 -0
  223. crewplane/observability/log_presentation/json_extract.py +348 -0
  224. crewplane/observability/log_presentation/limits.py +47 -0
  225. crewplane/observability/log_presentation/models.py +47 -0
  226. crewplane/observability/log_presentation/sanitize.py +82 -0
  227. crewplane/observability/log_presentation/tail.py +144 -0
  228. crewplane/observability/log_presentation/throttle.py +68 -0
  229. crewplane/observability/log_stream.py +100 -0
  230. crewplane/observability/node_order.py +29 -0
  231. crewplane/observability/observer.py +22 -0
  232. crewplane/observability/observer_lifecycle.py +173 -0
  233. crewplane/observability/persistent.py +47 -0
  234. crewplane/observability/render/__init__.py +196 -0
  235. crewplane/observability/render/cells.py +193 -0
  236. crewplane/observability/render/header.py +23 -0
  237. crewplane/observability/render/text.py +26 -0
  238. crewplane/observability/render/timeline.py +137 -0
  239. crewplane/observability/render/viewport.py +124 -0
  240. crewplane/observability/run_summary/accumulator.py +76 -0
  241. crewplane/observability/run_summary/builder.py +177 -0
  242. crewplane/observability/run_summary/formatting.py +64 -0
  243. crewplane/observability/run_summary/issues.py +167 -0
  244. crewplane/observability/run_summary/logger.py +170 -0
  245. crewplane/observability/run_summary/markdown.py +391 -0
  246. crewplane/observability/run_summary/models.py +301 -0
  247. crewplane/observability/run_summary/spend.py +268 -0
  248. crewplane/observability/run_summary/terminal.py +204 -0
  249. crewplane/observability/run_summary/workspace.py +187 -0
  250. crewplane/observability/run_summary/workspace_readers.py +395 -0
  251. crewplane/observability/run_summary/workspace_state_paths.py +33 -0
  252. crewplane/observability/run_summary/workspace_values.py +124 -0
  253. crewplane/observability/runtime.py +376 -0
  254. crewplane/observability/status_icons.py +14 -0
  255. crewplane/observability/text_layout.py +111 -0
  256. crewplane/observability/timing.py +56 -0
  257. crewplane/observability/tmux/__init__.py +36 -0
  258. crewplane/observability/tmux/bindings.py +273 -0
  259. crewplane/observability/tmux/client.py +141 -0
  260. crewplane/observability/tmux/commands.py +374 -0
  261. crewplane/observability/tmux/compact.py +199 -0
  262. crewplane/observability/tmux/control_state.py +89 -0
  263. crewplane/observability/tmux/inspect_control.py +189 -0
  264. crewplane/observability/tmux/inspect_launcher.py +67 -0
  265. crewplane/observability/tmux/inspect_snapshot.py +71 -0
  266. crewplane/observability/tmux/labels.py +24 -0
  267. crewplane/observability/tmux/log_tail.py +165 -0
  268. crewplane/observability/tmux/refresh.py +362 -0
  269. crewplane/observability/tmux/rendering.py +406 -0
  270. crewplane/observability/tmux/runtime.py +15 -0
  271. crewplane/observability/tmux/runtime_files.py +88 -0
  272. crewplane/observability/tmux/selected_invocation.py +101 -0
  273. crewplane/observability/tmux/selection.py +126 -0
  274. crewplane/observability/tmux/selection_control.py +159 -0
  275. crewplane/observability/tmux/session.py +43 -0
  276. crewplane/observability/tmux/session_lifecycle.py +345 -0
  277. crewplane/observability/tmux/shell_commands.py +76 -0
  278. crewplane/observability/tmux/viewport.py +77 -0
  279. crewplane/observability/tmux/window.py +134 -0
  280. crewplane/observability/types.py +45 -0
  281. crewplane/py.typed +1 -0
  282. crewplane/runtime/__init__.py +3 -0
  283. crewplane/runtime/agent/__init__.py +5 -0
  284. crewplane/runtime/agent/failures/__init__.py +80 -0
  285. crewplane/runtime/agent/failures/classifier.py +22 -0
  286. crewplane/runtime/agent/failures/evidence.py +249 -0
  287. crewplane/runtime/agent/failures/formatting.py +108 -0
  288. crewplane/runtime/agent/failures/patterns.py +172 -0
  289. crewplane/runtime/agent/failures/types.py +73 -0
  290. crewplane/runtime/agent/invocation/claude_json.py +368 -0
  291. crewplane/runtime/agent/invocation/command.py +239 -0
  292. crewplane/runtime/agent/invocation/loop.py +395 -0
  293. crewplane/runtime/agent/invocation/output.py +295 -0
  294. crewplane/runtime/agent/invocation/retry.py +228 -0
  295. crewplane/runtime/agent/invocation/retry_reset.py +32 -0
  296. crewplane/runtime/agent/invocation/state.py +161 -0
  297. crewplane/runtime/agent/invocation/telemetry.py +166 -0
  298. crewplane/runtime/agent/invocation/transitions.py +181 -0
  299. crewplane/runtime/agent/invoker.py +131 -0
  300. crewplane/runtime/agent/process/diagnostics.py +70 -0
  301. crewplane/runtime/agent/process/log_rendering.py +18 -0
  302. crewplane/runtime/agent/process/runner.py +26 -0
  303. crewplane/runtime/agent/process/signals.py +84 -0
  304. crewplane/runtime/agent/process/stream_capture.py +105 -0
  305. crewplane/runtime/agent/process/streams.py +376 -0
  306. crewplane/runtime/agent/quota/__init__.py +5 -0
  307. crewplane/runtime/agent/quota/classifier.py +75 -0
  308. crewplane/runtime/agent/quota/evidence.py +124 -0
  309. crewplane/runtime/agent/quota/lexicons.py +251 -0
  310. crewplane/runtime/agent/quota/waits.py +208 -0
  311. crewplane/runtime/agent/retry_units.py +49 -0
  312. crewplane/runtime/agent/usage.py +224 -0
  313. crewplane/runtime/agent/usage_costs.py +143 -0
  314. crewplane/runtime/agent/usage_parsing.py +210 -0
  315. crewplane/runtime/agent/usage_types.py +53 -0
  316. crewplane/runtime/agent/workspace_environment.py +63 -0
  317. crewplane/runtime/execution/__init__.py +13 -0
  318. crewplane/runtime/execution/activity/__init__.py +8 -0
  319. crewplane/runtime/execution/activity/console.py +38 -0
  320. crewplane/runtime/execution/activity/events.py +362 -0
  321. crewplane/runtime/execution/activity/telemetry.py +65 -0
  322. crewplane/runtime/execution/common.py +70 -0
  323. crewplane/runtime/execution/consensus.py +54 -0
  324. crewplane/runtime/execution/deferred_cleanup.py +137 -0
  325. crewplane/runtime/execution/fragment_assembler.py +260 -0
  326. crewplane/runtime/execution/input.py +75 -0
  327. crewplane/runtime/execution/log_presentation.py +61 -0
  328. crewplane/runtime/execution/parallel.py +263 -0
  329. crewplane/runtime/execution/prompt_budgeting.py +192 -0
  330. crewplane/runtime/execution/provider_call/__init__.py +63 -0
  331. crewplane/runtime/execution/provider_call/display.py +117 -0
  332. crewplane/runtime/execution/provider_call/events.py +114 -0
  333. crewplane/runtime/execution/provider_call/generated_file_changes.py +297 -0
  334. crewplane/runtime/execution/provider_call/generated_files.py +335 -0
  335. crewplane/runtime/execution/provider_call/lifecycle.py +470 -0
  336. crewplane/runtime/execution/provider_call/types.py +46 -0
  337. crewplane/runtime/execution/provider_call/workspace.py +133 -0
  338. crewplane/runtime/execution/resume.py +100 -0
  339. crewplane/runtime/execution/review_loop/__init__.py +481 -0
  340. crewplane/runtime/execution/review_loop/audit_round.py +357 -0
  341. crewplane/runtime/execution/review_loop/drift.py +159 -0
  342. crewplane/runtime/execution/review_loop/drift_detection.py +343 -0
  343. crewplane/runtime/execution/review_loop/drift_events.py +82 -0
  344. crewplane/runtime/execution/review_loop/executor_round.py +118 -0
  345. crewplane/runtime/execution/review_loop/policy.py +96 -0
  346. crewplane/runtime/execution/review_loop/prompts.py +199 -0
  347. crewplane/runtime/execution/review_loop/reviewer_round.py +334 -0
  348. crewplane/runtime/execution/review_loop/rounds.py +11 -0
  349. crewplane/runtime/execution/review_loop/state.py +314 -0
  350. crewplane/runtime/execution/review_loop/types.py +379 -0
  351. crewplane/runtime/execution/review_loop/validation.py +367 -0
  352. crewplane/runtime/execution/review_loop/workspace_state_paths.py +95 -0
  353. crewplane/runtime/execution/reviews/__init__.py +1 -0
  354. crewplane/runtime/execution/reviews/consensus.py +203 -0
  355. crewplane/runtime/execution/reviews/fingerprints.py +147 -0
  356. crewplane/runtime/execution/reviews/markdown.py +68 -0
  357. crewplane/runtime/execution/reviews/plain_language.py +124 -0
  358. crewplane/runtime/execution/reviews/structured.py +353 -0
  359. crewplane/runtime/execution/reviews/types.py +50 -0
  360. crewplane/runtime/execution/runtime_context.py +186 -0
  361. crewplane/runtime/execution/sequential.py +114 -0
  362. crewplane/runtime/execution/stage_finalize_events.py +62 -0
  363. crewplane/runtime/execution/stage_tasks.py +76 -0
  364. crewplane/runtime/execution/workflow/__init__.py +397 -0
  365. crewplane/runtime/execution/workflow/cleanup.py +170 -0
  366. crewplane/runtime/execution/workflow/node.py +128 -0
  367. crewplane/runtime/execution/workflow/state.py +94 -0
  368. crewplane/runtime/execution/workspace_files/__init__.py +394 -0
  369. crewplane/runtime/execution/workspace_files/generated.py +149 -0
  370. crewplane/runtime/execution/workspace_files/source_resolution.py +155 -0
  371. crewplane/runtime/workspace/__init__.py +8 -0
  372. crewplane/runtime/workspace/branch_export/__init__.py +389 -0
  373. crewplane/runtime/workspace/branch_export/checkpoint.py +246 -0
  374. crewplane/runtime/workspace/branch_export/fulfillment.py +194 -0
  375. crewplane/runtime/workspace/branch_export/git.py +141 -0
  376. crewplane/runtime/workspace/branch_export/records.py +233 -0
  377. crewplane/runtime/workspace/cleanup.py +254 -0
  378. crewplane/runtime/workspace/cleanup_notes.py +9 -0
  379. crewplane/runtime/workspace/git.py +73 -0
  380. crewplane/runtime/workspace/invocation.py +94 -0
  381. crewplane/runtime/workspace/locks.py +89 -0
  382. crewplane/runtime/workspace/materialization.py +42 -0
  383. crewplane/runtime/workspace/prepared_workspace.py +407 -0
  384. crewplane/runtime/workspace/service/__init__.py +64 -0
  385. crewplane/runtime/workspace/service/common.py +165 -0
  386. crewplane/runtime/workspace/service/snapshot.py +248 -0
  387. crewplane/runtime/workspace/service/types.py +81 -0
  388. crewplane/runtime/workspace/service/worktree.py +478 -0
  389. crewplane/runtime/workspace/service/worktree_failures.py +136 -0
  390. crewplane/runtime/workspace/setup.py +409 -0
  391. crewplane/runtime/workspace/snapshot.py +332 -0
  392. crewplane/runtime/workspace/state.py +365 -0
  393. crewplane/runtime/workspace/state_selection.py +268 -0
  394. crewplane/runtime/workspace/worktree/__init__.py +425 -0
  395. crewplane/runtime/workspace/worktree/attributes.py +33 -0
  396. crewplane/runtime/workspace/worktree/cache.py +291 -0
  397. crewplane/runtime/workspace/worktree/cleanup.py +222 -0
  398. crewplane/runtime/workspace/worktree/commit.py +52 -0
  399. crewplane/runtime/workspace/worktree/descriptors.py +185 -0
  400. crewplane/runtime/workspace/worktree/inspection.py +386 -0
  401. crewplane/runtime/workspace/worktree/lineage.py +434 -0
  402. crewplane/runtime/workspace/worktree/materialization.py +139 -0
  403. crewplane/runtime/workspace/worktree/policy.py +107 -0
  404. crewplane/runtime/workspace/worktree/protected_refs.py +63 -0
  405. crewplane/runtime/workspace/worktree/ref_cleanup.py +79 -0
  406. crewplane/runtime/workspace/worktree/refs.py +57 -0
  407. crewplane/runtime/workspace/worktree/reset.py +234 -0
  408. crewplane/runtime/workspace/worktree/result_validation.py +44 -0
  409. crewplane/runtime/workspace/worktree/reuse.py +95 -0
  410. crewplane/runtime/workspace/worktree/source_refs.py +90 -0
  411. crewplane/runtime/workspace/worktree/types.py +69 -0
  412. crewplane/version.py +5 -0
  413. crewplane-0.1.0.dist-info/METADATA +211 -0
  414. crewplane-0.1.0.dist-info/RECORD +417 -0
  415. crewplane-0.1.0.dist-info/WHEEL +4 -0
  416. crewplane-0.1.0.dist-info/entry_points.txt +2 -0
  417. crewplane-0.1.0.dist-info/licenses/LICENSE +201 -0
crewplane/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ from crewplane.version import SCHEMA_VERSION
2
+
3
+ __all__ = ["SCHEMA_VERSION"]
@@ -0,0 +1 @@
1
+ __all__: list[str] = []
@@ -0,0 +1,3 @@
1
+ from .filesystem import FilesystemArtifactsAdapter
2
+
3
+ __all__ = ["FilesystemArtifactsAdapter"]
@@ -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,4 @@
1
+ from .cli import CliInvokerAdapter
2
+ from .mock import MockInvokerAdapter
3
+
4
+ __all__ = ["CliInvokerAdapter", "MockInvokerAdapter"]
@@ -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,4 @@
1
+ from .invoker import MockAgentInvoker
2
+ from .options import parse_options
3
+
4
+ __all__ = ["MockAgentInvoker", "parse_options"]
@@ -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)