fdsx 0.2.2__tar.gz → 0.2.4__tar.gz

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 (272) hide show
  1. {fdsx-0.2.2 → fdsx-0.2.4}/PKG-INFO +37 -13
  2. {fdsx-0.2.2 → fdsx-0.2.4}/README.md +37 -13
  3. {fdsx-0.2.2 → fdsx-0.2.4}/src/fdsx/cli/main.py +31 -35
  4. {fdsx-0.2.2 → fdsx-0.2.4}/src/fdsx/core/batch.py +0 -160
  5. {fdsx-0.2.2 → fdsx-0.2.4}/src/fdsx/core/engine/__init__.py +0 -2
  6. {fdsx-0.2.2 → fdsx-0.2.4}/src/fdsx/core/engine/tasks_dir.py +38 -10
  7. fdsx-0.2.4/src/fdsx/core/mode.py +36 -0
  8. {fdsx-0.2.2 → fdsx-0.2.4}/src/fdsx/core/selector.py +20 -0
  9. {fdsx-0.2.2 → fdsx-0.2.4}/src/fdsx/data/skills/fdsx/SKILL.md +47 -6
  10. {fdsx-0.2.2 → fdsx-0.2.4}/src/fdsx/data/skills/fdsx/references/yaml-schema.md +76 -0
  11. {fdsx-0.2.2 → fdsx-0.2.4}/src/fdsx/display/terminal.py +12 -8
  12. {fdsx-0.2.2 → fdsx-0.2.4}/src/fdsx.egg-info/PKG-INFO +37 -13
  13. {fdsx-0.2.2 → fdsx-0.2.4}/src/fdsx.egg-info/SOURCES.txt +3 -4
  14. {fdsx-0.2.2 → fdsx-0.2.4}/tests/e2e/test_batch_full_pipeline.py +0 -11
  15. {fdsx-0.2.2 → fdsx-0.2.4}/tests/e2e/test_cli_wait_and_resume.py +3 -2
  16. {fdsx-0.2.2 → fdsx-0.2.4}/tests/integration/test_auto_init_cli.py +23 -16
  17. fdsx-0.2.4/tests/integration/test_ci_cli.py +178 -0
  18. fdsx-0.2.4/tests/integration/test_ci_mode.py +292 -0
  19. {fdsx-0.2.2 → fdsx-0.2.4}/tests/integration/test_single_task_confirm.py +2 -2
  20. {fdsx-0.2.2 → fdsx-0.2.4}/tests/integration/test_tasks_dir.py +77 -57
  21. {fdsx-0.2.2 → fdsx-0.2.4}/tests/integration/test_workflow_persistence.py +5 -5
  22. {fdsx-0.2.2 → fdsx-0.2.4}/tests/unit/test_batch.py +0 -371
  23. {fdsx-0.2.2 → fdsx-0.2.4}/tests/unit/test_selector.py +90 -53
  24. {fdsx-0.2.2 → fdsx-0.2.4}/tests/unit/test_spinner.py +45 -38
  25. {fdsx-0.2.2 → fdsx-0.2.4}/tests/unit/test_terminal.py +136 -101
  26. {fdsx-0.2.2 → fdsx-0.2.4}/tests/unit/test_workflow_cui.py +16 -16
  27. fdsx-0.2.2/src/fdsx/core/engine/batch.py +0 -132
  28. fdsx-0.2.2/tests/e2e/test_batch_backward_compat.py +0 -73
  29. fdsx-0.2.2/tests/e2e/test_cli_batch_split.py +0 -263
  30. fdsx-0.2.2/tests/e2e/test_cli_batch_tasks.py +0 -112
  31. {fdsx-0.2.2 → fdsx-0.2.4}/.github/dependabot.yml +0 -0
  32. {fdsx-0.2.2 → fdsx-0.2.4}/.github/workflows/publish.yml +0 -0
  33. {fdsx-0.2.2 → fdsx-0.2.4}/.github/workflows/test.yml +0 -0
  34. {fdsx-0.2.2 → fdsx-0.2.4}/.pre-commit-config.yaml +0 -0
  35. {fdsx-0.2.2 → fdsx-0.2.4}/AGENTS.md +0 -0
  36. {fdsx-0.2.2 → fdsx-0.2.4}/CLAUDE.md +0 -0
  37. {fdsx-0.2.2 → fdsx-0.2.4}/GEMINI.md +0 -0
  38. {fdsx-0.2.2 → fdsx-0.2.4}/LICENSE +0 -0
  39. {fdsx-0.2.2 → fdsx-0.2.4}/MANIFEST.in +0 -0
  40. {fdsx-0.2.2 → fdsx-0.2.4}/examples/self-improve/README.md +0 -0
  41. {fdsx-0.2.2 → fdsx-0.2.4}/examples/self-improve/analyze.md +0 -0
  42. {fdsx-0.2.2 → fdsx-0.2.4}/examples/self-improve/collect_data.sh +0 -0
  43. {fdsx-0.2.2 → fdsx-0.2.4}/examples/self-improve/research.md +0 -0
  44. {fdsx-0.2.2 → fdsx-0.2.4}/examples/self-improve/workflow.yaml +0 -0
  45. {fdsx-0.2.2 → fdsx-0.2.4}/examples/self-improve/write_lessons.md +0 -0
  46. {fdsx-0.2.2 → fdsx-0.2.4}/pyproject.toml +0 -0
  47. {fdsx-0.2.2 → fdsx-0.2.4}/setup.cfg +0 -0
  48. {fdsx-0.2.2 → fdsx-0.2.4}/src/fdsx/__init__.py +0 -0
  49. {fdsx-0.2.2 → fdsx-0.2.4}/src/fdsx/checkpoint/__init__.py +0 -0
  50. {fdsx-0.2.2 → fdsx-0.2.4}/src/fdsx/checkpoint/manager.py +0 -0
  51. {fdsx-0.2.2 → fdsx-0.2.4}/src/fdsx/cli/__init__.py +0 -0
  52. {fdsx-0.2.2 → fdsx-0.2.4}/src/fdsx/cli/init_interactive.py +0 -0
  53. {fdsx-0.2.2 → fdsx-0.2.4}/src/fdsx/core/__init__.py +0 -0
  54. {fdsx-0.2.2 → fdsx-0.2.4}/src/fdsx/core/compiler/__init__.py +0 -0
  55. {fdsx-0.2.2 → fdsx-0.2.4}/src/fdsx/core/compiler/aggregation.py +0 -0
  56. {fdsx-0.2.2 → fdsx-0.2.4}/src/fdsx/core/compiler/compile.py +0 -0
  57. {fdsx-0.2.2 → fdsx-0.2.4}/src/fdsx/core/compiler/execution.py +0 -0
  58. {fdsx-0.2.2 → fdsx-0.2.4}/src/fdsx/core/compiler/helpers.py +0 -0
  59. {fdsx-0.2.2 → fdsx-0.2.4}/src/fdsx/core/compiler/map_iteration.py +0 -0
  60. {fdsx-0.2.2 → fdsx-0.2.4}/src/fdsx/core/compiler/nodes.py +0 -0
  61. {fdsx-0.2.2 → fdsx-0.2.4}/src/fdsx/core/compiler/parallel.py +0 -0
  62. {fdsx-0.2.2 → fdsx-0.2.4}/src/fdsx/core/compiler/routing.py +0 -0
  63. {fdsx-0.2.2 → fdsx-0.2.4}/src/fdsx/core/config.py +0 -0
  64. {fdsx-0.2.2 → fdsx-0.2.4}/src/fdsx/core/engine/interrupts.py +0 -0
  65. {fdsx-0.2.2 → fdsx-0.2.4}/src/fdsx/core/engine/results.py +0 -0
  66. {fdsx-0.2.2 → fdsx-0.2.4}/src/fdsx/core/engine/resume.py +0 -0
  67. {fdsx-0.2.2 → fdsx-0.2.4}/src/fdsx/core/engine/run.py +0 -0
  68. {fdsx-0.2.2 → fdsx-0.2.4}/src/fdsx/core/engine/signals.py +0 -0
  69. {fdsx-0.2.2 → fdsx-0.2.4}/src/fdsx/core/engine/validate.py +0 -0
  70. {fdsx-0.2.2 → fdsx-0.2.4}/src/fdsx/core/extraction.py +0 -0
  71. {fdsx-0.2.2 → fdsx-0.2.4}/src/fdsx/core/graph_utils.py +0 -0
  72. {fdsx-0.2.2 → fdsx-0.2.4}/src/fdsx/core/hooks.py +0 -0
  73. {fdsx-0.2.2 → fdsx-0.2.4}/src/fdsx/core/init.py +0 -0
  74. {fdsx-0.2.2 → fdsx-0.2.4}/src/fdsx/core/loader.py +0 -0
  75. {fdsx-0.2.2 → fdsx-0.2.4}/src/fdsx/core/paths.py +0 -0
  76. {fdsx-0.2.2 → fdsx-0.2.4}/src/fdsx/core/profiles.py +0 -0
  77. {fdsx-0.2.2 → fdsx-0.2.4}/src/fdsx/core/thread_id.py +0 -0
  78. {fdsx-0.2.2 → fdsx-0.2.4}/src/fdsx/core/variables.py +0 -0
  79. {fdsx-0.2.2 → fdsx-0.2.4}/src/fdsx/data/__init__.py +0 -0
  80. {fdsx-0.2.2 → fdsx-0.2.4}/src/fdsx/data/skills/__init__.py +0 -0
  81. {fdsx-0.2.2 → fdsx-0.2.4}/src/fdsx/display/__init__.py +0 -0
  82. {fdsx-0.2.2 → fdsx-0.2.4}/src/fdsx/examples/__init__.py +0 -0
  83. {fdsx-0.2.2 → fdsx-0.2.4}/src/fdsx/examples/workflows/__init__.py +0 -0
  84. {fdsx-0.2.2 → fdsx-0.2.4}/src/fdsx/examples/workflows/full-impl/finalize.md +0 -0
  85. {fdsx-0.2.2 → fdsx-0.2.4}/src/fdsx/examples/workflows/full-impl/fix.md +0 -0
  86. {fdsx-0.2.2 → fdsx-0.2.4}/src/fdsx/examples/workflows/full-impl/implement.md +0 -0
  87. {fdsx-0.2.2 → fdsx-0.2.4}/src/fdsx/examples/workflows/full-impl/plan.md +0 -0
  88. {fdsx-0.2.2 → fdsx-0.2.4}/src/fdsx/examples/workflows/full-impl/replan.md +0 -0
  89. {fdsx-0.2.2 → fdsx-0.2.4}/src/fdsx/examples/workflows/full-impl/review-code-quality.md +0 -0
  90. {fdsx-0.2.2 → fdsx-0.2.4}/src/fdsx/examples/workflows/full-impl/review-security.md +0 -0
  91. {fdsx-0.2.2 → fdsx-0.2.4}/src/fdsx/examples/workflows/full-impl/workflow.yaml +0 -0
  92. {fdsx-0.2.2 → fdsx-0.2.4}/src/fdsx/examples/workflows/self-improve/README.md +0 -0
  93. {fdsx-0.2.2 → fdsx-0.2.4}/src/fdsx/examples/workflows/self-improve/analyze.md +0 -0
  94. {fdsx-0.2.2 → fdsx-0.2.4}/src/fdsx/examples/workflows/self-improve/collect_data.sh +0 -0
  95. {fdsx-0.2.2 → fdsx-0.2.4}/src/fdsx/examples/workflows/self-improve/research.md +0 -0
  96. {fdsx-0.2.2 → fdsx-0.2.4}/src/fdsx/examples/workflows/self-improve/workflow.yaml +0 -0
  97. {fdsx-0.2.2 → fdsx-0.2.4}/src/fdsx/examples/workflows/self-improve/write_lessons.md +0 -0
  98. {fdsx-0.2.2 → fdsx-0.2.4}/src/fdsx/examples/workflows/simple-impl/finalize.md +0 -0
  99. {fdsx-0.2.2 → fdsx-0.2.4}/src/fdsx/examples/workflows/simple-impl/fix.md +0 -0
  100. {fdsx-0.2.2 → fdsx-0.2.4}/src/fdsx/examples/workflows/simple-impl/implement.md +0 -0
  101. {fdsx-0.2.2 → fdsx-0.2.4}/src/fdsx/examples/workflows/simple-impl/plan.md +0 -0
  102. {fdsx-0.2.2 → fdsx-0.2.4}/src/fdsx/examples/workflows/simple-impl/replan.md +0 -0
  103. {fdsx-0.2.2 → fdsx-0.2.4}/src/fdsx/examples/workflows/simple-impl/review-general.md +0 -0
  104. {fdsx-0.2.2 → fdsx-0.2.4}/src/fdsx/examples/workflows/simple-impl/workflow.yaml +0 -0
  105. {fdsx-0.2.2 → fdsx-0.2.4}/src/fdsx/logging/__init__.py +0 -0
  106. {fdsx-0.2.2 → fdsx-0.2.4}/src/fdsx/logging/recorder.py +0 -0
  107. {fdsx-0.2.2 → fdsx-0.2.4}/src/fdsx/logging/stream_logger.py +0 -0
  108. {fdsx-0.2.2 → fdsx-0.2.4}/src/fdsx/models/__init__.py +0 -0
  109. {fdsx-0.2.2 → fdsx-0.2.4}/src/fdsx/models/flow.py +0 -0
  110. {fdsx-0.2.2 → fdsx-0.2.4}/src/fdsx/models/init.py +0 -0
  111. {fdsx-0.2.2 → fdsx-0.2.4}/src/fdsx/models/task.py +0 -0
  112. {fdsx-0.2.2 → fdsx-0.2.4}/src/fdsx/models/validators.py +0 -0
  113. {fdsx-0.2.2 → fdsx-0.2.4}/src/fdsx/notify/__init__.py +0 -0
  114. {fdsx-0.2.2 → fdsx-0.2.4}/src/fdsx/notify/webhook.py +0 -0
  115. {fdsx-0.2.2 → fdsx-0.2.4}/src/fdsx/providers/__init__.py +0 -0
  116. {fdsx-0.2.2 → fdsx-0.2.4}/src/fdsx/providers/base.py +0 -0
  117. {fdsx-0.2.2 → fdsx-0.2.4}/src/fdsx/providers/claude.py +0 -0
  118. {fdsx-0.2.2 → fdsx-0.2.4}/src/fdsx/providers/codex.py +0 -0
  119. {fdsx-0.2.2 → fdsx-0.2.4}/src/fdsx/providers/gemini.py +0 -0
  120. {fdsx-0.2.2 → fdsx-0.2.4}/src/fdsx/providers/opencode.py +0 -0
  121. {fdsx-0.2.2 → fdsx-0.2.4}/src/fdsx/providers/system.py +0 -0
  122. {fdsx-0.2.2 → fdsx-0.2.4}/src/fdsx/py.typed +0 -0
  123. {fdsx-0.2.2 → fdsx-0.2.4}/src/fdsx.egg-info/dependency_links.txt +0 -0
  124. {fdsx-0.2.2 → fdsx-0.2.4}/src/fdsx.egg-info/entry_points.txt +0 -0
  125. {fdsx-0.2.2 → fdsx-0.2.4}/src/fdsx.egg-info/requires.txt +0 -0
  126. {fdsx-0.2.2 → fdsx-0.2.4}/src/fdsx.egg-info/top_level.txt +0 -0
  127. {fdsx-0.2.2 → fdsx-0.2.4}/tests/__init__.py +0 -0
  128. {fdsx-0.2.2 → fdsx-0.2.4}/tests/conftest.py +0 -0
  129. {fdsx-0.2.2 → fdsx-0.2.4}/tests/e2e/__init__.py +0 -0
  130. {fdsx-0.2.2 → fdsx-0.2.4}/tests/e2e/cli_test_utils.py +0 -0
  131. {fdsx-0.2.2 → fdsx-0.2.4}/tests/e2e/conftest.py +0 -0
  132. {fdsx-0.2.2 → fdsx-0.2.4}/tests/e2e/test_add_cli.py +0 -0
  133. {fdsx-0.2.2 → fdsx-0.2.4}/tests/e2e/test_auto_init.py +0 -0
  134. {fdsx-0.2.2 → fdsx-0.2.4}/tests/e2e/test_batch_edge_cases.py +0 -0
  135. {fdsx-0.2.2 → fdsx-0.2.4}/tests/e2e/test_batch_error_messages.py +0 -0
  136. {fdsx-0.2.2 → fdsx-0.2.4}/tests/e2e/test_cli_flow_types.py +0 -0
  137. {fdsx-0.2.2 → fdsx-0.2.4}/tests/e2e/test_cli_map_flow.py +0 -0
  138. {fdsx-0.2.2 → fdsx-0.2.4}/tests/e2e/test_cli_signal_handling.py +0 -0
  139. {fdsx-0.2.2 → fdsx-0.2.4}/tests/e2e/test_cli_thread_id.py +0 -0
  140. {fdsx-0.2.2 → fdsx-0.2.4}/tests/e2e/test_cli_validation_and_run.py +0 -0
  141. {fdsx-0.2.2 → fdsx-0.2.4}/tests/e2e/test_cli_workflow_name.py +0 -0
  142. {fdsx-0.2.2 → fdsx-0.2.4}/tests/fixtures/batch_flow.yaml +0 -0
  143. {fdsx-0.2.2 → fdsx-0.2.4}/tests/fixtures/checkpoint_flow.yaml +0 -0
  144. {fdsx-0.2.2 → fdsx-0.2.4}/tests/fixtures/choice_flow.yaml +0 -0
  145. {fdsx-0.2.2 → fdsx-0.2.4}/tests/fixtures/choice_flow_default.yaml +0 -0
  146. {fdsx-0.2.2 → fdsx-0.2.4}/tests/fixtures/claude_stream.ndjson +0 -0
  147. {fdsx-0.2.2 → fdsx-0.2.4}/tests/fixtures/codex_stream.jsonl +0 -0
  148. {fdsx-0.2.2 → fdsx-0.2.4}/tests/fixtures/extraction_flow.yaml +0 -0
  149. {fdsx-0.2.2 → fdsx-0.2.4}/tests/fixtures/input_flow.yaml +0 -0
  150. {fdsx-0.2.2 → fdsx-0.2.4}/tests/fixtures/invalid_flows/bad_next_ref.yaml +0 -0
  151. {fdsx-0.2.2 → fdsx-0.2.4}/tests/fixtures/invalid_flows/missing_start_at.yaml +0 -0
  152. {fdsx-0.2.2 → fdsx-0.2.4}/tests/fixtures/invalid_flows/mutual_exclusive.yaml +0 -0
  153. {fdsx-0.2.2 → fdsx-0.2.4}/tests/fixtures/json_codeblock_extraction_flow.yaml +0 -0
  154. {fdsx-0.2.2 → fdsx-0.2.4}/tests/fixtures/json_extraction_flow.yaml +0 -0
  155. {fdsx-0.2.2 → fdsx-0.2.4}/tests/fixtures/loop_flow.yaml +0 -0
  156. {fdsx-0.2.2 → fdsx-0.2.4}/tests/fixtures/map_basic.yaml +0 -0
  157. {fdsx-0.2.2 → fdsx-0.2.4}/tests/fixtures/map_empty_items.yaml +0 -0
  158. {fdsx-0.2.2 → fdsx-0.2.4}/tests/fixtures/map_fail_fast_false.yaml +0 -0
  159. {fdsx-0.2.2 → fdsx-0.2.4}/tests/fixtures/map_inside_parallel.yaml +0 -0
  160. {fdsx-0.2.2 → fdsx-0.2.4}/tests/fixtures/max_iterations_flow.yaml +0 -0
  161. {fdsx-0.2.2 → fdsx-0.2.4}/tests/fixtures/max_iterations_wait_flow.yaml +0 -0
  162. {fdsx-0.2.2 → fdsx-0.2.4}/tests/fixtures/parallel_min_success.yaml +0 -0
  163. {fdsx-0.2.2 → fdsx-0.2.4}/tests/fixtures/parallel_review.yaml +0 -0
  164. {fdsx-0.2.2 → fdsx-0.2.4}/tests/fixtures/profile_flow.yaml +0 -0
  165. {fdsx-0.2.2 → fdsx-0.2.4}/tests/fixtures/profile_parallel_flow.yaml +0 -0
  166. {fdsx-0.2.2 → fdsx-0.2.4}/tests/fixtures/prompt_file_test/flow.yaml +0 -0
  167. {fdsx-0.2.2 → fdsx-0.2.4}/tests/fixtures/prompt_file_test/prompt.txt +0 -0
  168. {fdsx-0.2.2 → fdsx-0.2.4}/tests/fixtures/regex_extraction_flow.yaml +0 -0
  169. {fdsx-0.2.2 → fdsx-0.2.4}/tests/fixtures/sample_tasks.md +0 -0
  170. {fdsx-0.2.2 → fdsx-0.2.4}/tests/fixtures/self_improve_flow/collect_data.sh +0 -0
  171. {fdsx-0.2.2 → fdsx-0.2.4}/tests/fixtures/self_improve_flow/workflow.yaml +0 -0
  172. {fdsx-0.2.2 → fdsx-0.2.4}/tests/fixtures/simple_flow.yaml +0 -0
  173. {fdsx-0.2.2 → fdsx-0.2.4}/tests/fixtures/wait_approval.yaml +0 -0
  174. {fdsx-0.2.2 → fdsx-0.2.4}/tests/fixtures/wait_resume_flow.yaml +0 -0
  175. {fdsx-0.2.2 → fdsx-0.2.4}/tests/fixtures/wait_webhook.yaml +0 -0
  176. {fdsx-0.2.2 → fdsx-0.2.4}/tests/fixtures/workflows_name_display/alpha.yaml +0 -0
  177. {fdsx-0.2.2 → fdsx-0.2.4}/tests/fixtures/workflows_name_display/beta/workflow.yaml +0 -0
  178. {fdsx-0.2.2 → fdsx-0.2.4}/tests/fixtures/workflows_name_display/gamma.yaml +0 -0
  179. {fdsx-0.2.2 → fdsx-0.2.4}/tests/integration/__init__.py +0 -0
  180. {fdsx-0.2.2 → fdsx-0.2.4}/tests/integration/test_add_single_task.py +0 -0
  181. {fdsx-0.2.2 → fdsx-0.2.4}/tests/integration/test_auto_init.py +0 -0
  182. {fdsx-0.2.2 → fdsx-0.2.4}/tests/integration/test_auto_select.py +0 -0
  183. {fdsx-0.2.2 → fdsx-0.2.4}/tests/integration/test_checkpoint_resume.py +0 -0
  184. {fdsx-0.2.2 → fdsx-0.2.4}/tests/integration/test_choice_flow.py +0 -0
  185. {fdsx-0.2.2 → fdsx-0.2.4}/tests/integration/test_claude_streaming.py +0 -0
  186. {fdsx-0.2.2 → fdsx-0.2.4}/tests/integration/test_codex_stream_lifecycle.py +0 -0
  187. {fdsx-0.2.2 → fdsx-0.2.4}/tests/integration/test_codex_streaming.py +0 -0
  188. {fdsx-0.2.2 → fdsx-0.2.4}/tests/integration/test_default_tasks_dir.py +0 -0
  189. {fdsx-0.2.2 → fdsx-0.2.4}/tests/integration/test_extraction_flow.py +0 -0
  190. {fdsx-0.2.2 → fdsx-0.2.4}/tests/integration/test_gemini_provider.py +0 -0
  191. {fdsx-0.2.2 → fdsx-0.2.4}/tests/integration/test_hook_streaming_interaction.py +0 -0
  192. {fdsx-0.2.2 → fdsx-0.2.4}/tests/integration/test_hooks_integration.py +0 -0
  193. {fdsx-0.2.2 → fdsx-0.2.4}/tests/integration/test_inactivity_timeout.py +0 -0
  194. {fdsx-0.2.2 → fdsx-0.2.4}/tests/integration/test_init.py +0 -0
  195. {fdsx-0.2.2 → fdsx-0.2.4}/tests/integration/test_init_cli.py +0 -0
  196. {fdsx-0.2.2 → fdsx-0.2.4}/tests/integration/test_init_full_flow.py +0 -0
  197. {fdsx-0.2.2 → fdsx-0.2.4}/tests/integration/test_init_interactive.py +0 -0
  198. {fdsx-0.2.2 → fdsx-0.2.4}/tests/integration/test_init_templates.py +0 -0
  199. {fdsx-0.2.2 → fdsx-0.2.4}/tests/integration/test_iteration_logs.py +0 -0
  200. {fdsx-0.2.2 → fdsx-0.2.4}/tests/integration/test_large_command.py +0 -0
  201. {fdsx-0.2.2 → fdsx-0.2.4}/tests/integration/test_linear_flow.py +0 -0
  202. {fdsx-0.2.2 → fdsx-0.2.4}/tests/integration/test_lock_atomicity.py +0 -0
  203. {fdsx-0.2.2 → fdsx-0.2.4}/tests/integration/test_loop_enforcement.py +0 -0
  204. {fdsx-0.2.2 → fdsx-0.2.4}/tests/integration/test_loop_flow.py +0 -0
  205. {fdsx-0.2.2 → fdsx-0.2.4}/tests/integration/test_map_checkpoint.py +0 -0
  206. {fdsx-0.2.2 → fdsx-0.2.4}/tests/integration/test_map_flow.py +0 -0
  207. {fdsx-0.2.2 → fdsx-0.2.4}/tests/integration/test_max_iterations_flow.py +0 -0
  208. {fdsx-0.2.2 → fdsx-0.2.4}/tests/integration/test_parallel_flow.py +0 -0
  209. {fdsx-0.2.2 → fdsx-0.2.4}/tests/integration/test_profile_assignment.py +0 -0
  210. {fdsx-0.2.2 → fdsx-0.2.4}/tests/integration/test_profile_flow.py +0 -0
  211. {fdsx-0.2.2 → fdsx-0.2.4}/tests/integration/test_provider_options.py +0 -0
  212. {fdsx-0.2.2 → fdsx-0.2.4}/tests/integration/test_quiet_mode.py +0 -0
  213. {fdsx-0.2.2 → fdsx-0.2.4}/tests/integration/test_result_file.py +0 -0
  214. {fdsx-0.2.2 → fdsx-0.2.4}/tests/integration/test_resume_interrupt.py +0 -0
  215. {fdsx-0.2.2 → fdsx-0.2.4}/tests/integration/test_scaffold_gitignore.py +0 -0
  216. {fdsx-0.2.2 → fdsx-0.2.4}/tests/integration/test_scenario_flows.py +0 -0
  217. {fdsx-0.2.2 → fdsx-0.2.4}/tests/integration/test_self_improve_flow.py +0 -0
  218. {fdsx-0.2.2 → fdsx-0.2.4}/tests/integration/test_skill_install.py +0 -0
  219. {fdsx-0.2.2 → fdsx-0.2.4}/tests/integration/test_split.py +0 -0
  220. {fdsx-0.2.2 → fdsx-0.2.4}/tests/integration/test_split_spinner.py +0 -0
  221. {fdsx-0.2.2 → fdsx-0.2.4}/tests/integration/test_stream_output.py +0 -0
  222. {fdsx-0.2.2 → fdsx-0.2.4}/tests/integration/test_subprocess_completion.py +0 -0
  223. {fdsx-0.2.2 → fdsx-0.2.4}/tests/integration/test_system_prompt.py +0 -0
  224. {fdsx-0.2.2 → fdsx-0.2.4}/tests/integration/test_tool_use_streaming.py +0 -0
  225. {fdsx-0.2.2 → fdsx-0.2.4}/tests/integration/test_universal_scaffold_trigger.py +0 -0
  226. {fdsx-0.2.2 → fdsx-0.2.4}/tests/integration/test_wait_resume.py +0 -0
  227. {fdsx-0.2.2 → fdsx-0.2.4}/tests/unit/__init__.py +0 -0
  228. {fdsx-0.2.2 → fdsx-0.2.4}/tests/unit/test_aggregation.py +0 -0
  229. {fdsx-0.2.2 → fdsx-0.2.4}/tests/unit/test_backoff.py +0 -0
  230. {fdsx-0.2.2 → fdsx-0.2.4}/tests/unit/test_checkpoint.py +0 -0
  231. {fdsx-0.2.2 → fdsx-0.2.4}/tests/unit/test_claude_options.py +0 -0
  232. {fdsx-0.2.2 → fdsx-0.2.4}/tests/unit/test_claude_stream_parser.py +0 -0
  233. {fdsx-0.2.2 → fdsx-0.2.4}/tests/unit/test_cli_version.py +0 -0
  234. {fdsx-0.2.2 → fdsx-0.2.4}/tests/unit/test_codex_stream_parser.py +0 -0
  235. {fdsx-0.2.2 → fdsx-0.2.4}/tests/unit/test_compiler_merge.py +0 -0
  236. {fdsx-0.2.2 → fdsx-0.2.4}/tests/unit/test_config.py +0 -0
  237. {fdsx-0.2.2 → fdsx-0.2.4}/tests/unit/test_config_generation.py +0 -0
  238. {fdsx-0.2.2 → fdsx-0.2.4}/tests/unit/test_engine.py +0 -0
  239. {fdsx-0.2.2 → fdsx-0.2.4}/tests/unit/test_execution.py +0 -0
  240. {fdsx-0.2.2 → fdsx-0.2.4}/tests/unit/test_extraction.py +0 -0
  241. {fdsx-0.2.2 → fdsx-0.2.4}/tests/unit/test_format_tool_input_summary.py +0 -0
  242. {fdsx-0.2.2 → fdsx-0.2.4}/tests/unit/test_gemini_options.py +0 -0
  243. {fdsx-0.2.2 → fdsx-0.2.4}/tests/unit/test_gemini_stream.py +0 -0
  244. {fdsx-0.2.2 → fdsx-0.2.4}/tests/unit/test_gitignore.py +0 -0
  245. {fdsx-0.2.2 → fdsx-0.2.4}/tests/unit/test_graph_utils.py +0 -0
  246. {fdsx-0.2.2 → fdsx-0.2.4}/tests/unit/test_hooks.py +0 -0
  247. {fdsx-0.2.2 → fdsx-0.2.4}/tests/unit/test_loader.py +0 -0
  248. {fdsx-0.2.2 → fdsx-0.2.4}/tests/unit/test_map_model.py +0 -0
  249. {fdsx-0.2.2 → fdsx-0.2.4}/tests/unit/test_max_iterations.py +0 -0
  250. {fdsx-0.2.2 → fdsx-0.2.4}/tests/unit/test_models.py +0 -0
  251. {fdsx-0.2.2 → fdsx-0.2.4}/tests/unit/test_paths.py +0 -0
  252. {fdsx-0.2.2 → fdsx-0.2.4}/tests/unit/test_profile_fixtures.py +0 -0
  253. {fdsx-0.2.2 → fdsx-0.2.4}/tests/unit/test_profiles.py +0 -0
  254. {fdsx-0.2.2 → fdsx-0.2.4}/tests/unit/test_prompt_file.py +0 -0
  255. {fdsx-0.2.2 → fdsx-0.2.4}/tests/unit/test_provider_options.py +0 -0
  256. {fdsx-0.2.2 → fdsx-0.2.4}/tests/unit/test_provider_stdin_fallback.py +0 -0
  257. {fdsx-0.2.2 → fdsx-0.2.4}/tests/unit/test_recorder.py +0 -0
  258. {fdsx-0.2.2 → fdsx-0.2.4}/tests/unit/test_result_file.py +0 -0
  259. {fdsx-0.2.2 → fdsx-0.2.4}/tests/unit/test_resume_display.py +0 -0
  260. {fdsx-0.2.2 → fdsx-0.2.4}/tests/unit/test_selector_name.py +0 -0
  261. {fdsx-0.2.2 → fdsx-0.2.4}/tests/unit/test_signal_handler.py +0 -0
  262. {fdsx-0.2.2 → fdsx-0.2.4}/tests/unit/test_stream_callback.py +0 -0
  263. {fdsx-0.2.2 → fdsx-0.2.4}/tests/unit/test_stream_logger.py +0 -0
  264. {fdsx-0.2.2 → fdsx-0.2.4}/tests/unit/test_stream_logger_iteration.py +0 -0
  265. {fdsx-0.2.2 → fdsx-0.2.4}/tests/unit/test_subprocess_completion.py +0 -0
  266. {fdsx-0.2.2 → fdsx-0.2.4}/tests/unit/test_subprocess_realtime_streaming.py +0 -0
  267. {fdsx-0.2.2 → fdsx-0.2.4}/tests/unit/test_subprocess_stdin.py +0 -0
  268. {fdsx-0.2.2 → fdsx-0.2.4}/tests/unit/test_task_model.py +0 -0
  269. {fdsx-0.2.2 → fdsx-0.2.4}/tests/unit/test_task_splitter_prompt.py +0 -0
  270. {fdsx-0.2.2 → fdsx-0.2.4}/tests/unit/test_thread_id.py +0 -0
  271. {fdsx-0.2.2 → fdsx-0.2.4}/tests/unit/test_variables.py +0 -0
  272. {fdsx-0.2.2 → fdsx-0.2.4}/tests/unit/test_webhook.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fdsx
3
- Version: 0.2.2
3
+ Version: 0.2.4
4
4
  Summary: Declarative AI agent workflow execution framework
5
5
  Author: kenfdev
6
6
  License-Expression: MIT
@@ -50,9 +50,11 @@ fdsx enables you to define AI agent workflows in YAML, combining the durability
50
50
 
51
51
  **Key features:**
52
52
  - Declarative YAML-based workflow definition
53
+ - Interactive project initialization and scaffolding
53
54
  - Stateful execution with checkpoint/resume
54
55
  - Parallel execution with branch aggregation
55
- - Batch task processing (in-memory and persistent)
56
+ - Map state for iterating over arrays with sub-workflows
57
+ - Persistent batch task processing with crash-resilient resume
56
58
  - Multiple LLM provider support (Claude, Codex, Gemini, OpenCode, and system commands)
57
59
  - Named profiles for reusable provider/model configuration
58
60
  - Webhook notifications on wait states
@@ -74,6 +76,14 @@ uv tool install fdsx
74
76
 
75
77
  ## Quick Start
76
78
 
79
+ Initialize a new project:
80
+
81
+ ```bash
82
+ fdsx init
83
+ ```
84
+
85
+ This interactively scaffolds a `.fdsx/` directory with configuration and example workflows.
86
+
77
87
  Create a simple YAML workflow file:
78
88
 
79
89
  ```yaml
@@ -184,6 +194,8 @@ states:
184
194
  type: llm_classify # (literal, REQUIRED) only "llm_classify" supported
185
195
  provider: claude # (string, REQUIRED) LLM provider for classification
186
196
  prompt: "Classify as APPROVED or NEEDS_FIX" # (string, REQUIRED)
197
+ # Alternatively, use a profile reference (mutually exclusive with provider):
198
+ # profile: smarty
187
199
 
188
200
  # --- Execution control ---
189
201
  retry: 3 # (int, default: 3) retry attempts on failure
@@ -234,8 +246,10 @@ states:
234
246
  parallel_review:
235
247
  type: parallel # (REQUIRED) literal "parallel"
236
248
  branches: # (list, REQUIRED) each branch is an independent execution
237
- - provider: claude # same provider rules as task (or use profile:)
249
+ - provider: claude # same provider rules as task
238
250
  model: claude-sonnet-4-6
251
+ # Alternatively, use a profile reference (mutually exclusive with provider/model):
252
+ # profile: smarty
239
253
  prompt_template: |
240
254
  Review code quality: {implementation}
241
255
  # prompt_file: review.md # alternative to prompt_template
@@ -286,7 +300,7 @@ states:
286
300
  result_path: $.iter.step2
287
301
  retry: 0
288
302
  fail_fast: true # (bool, default: true) stop all iterations on first failure
289
- result_path: $.map_results # (string, optional) JSONPath for the results array
303
+ result_path: $.map_results # (string, REQUIRED) JSONPath for the results array
290
304
  max_iterations: 10 # (int, optional) max times this state can be re-entered
291
305
  hooks: # (optional)
292
306
  next: after_map # next / end — same rules as task
@@ -446,6 +460,10 @@ workflows_dir: .fdsx/workflows # (string, default: ".fdsx/workflows")
446
460
  # must be relative, no ".." components
447
461
  # where `fdsx run --tasks-dir` discovers workflows
448
462
 
463
+ # --- Default tasks directory ---
464
+ default_tasks_dir: .fdsx/tasks # (string, optional) default directory for bare `fdsx run`
465
+ # when no workflow, --tasks, or --tasks-dir is given
466
+
449
467
  # --- Auto-workflow selection ---
450
468
  auto_workflow: false # (bool, default: false) skip interactive confirmation UI
451
469
 
@@ -457,7 +475,7 @@ workflow_selector:
457
475
  extra_instructions: | # (string, optional) appended to the selection prompt
458
476
  Prefer simple-impl for small tasks.
459
477
 
460
- # --- Task splitter: LLM used by `fdsx split` ---
478
+ # --- Task splitter: LLM used by `fdsx add --split` ---
461
479
  task_splitter:
462
480
  profile: smarty # (string, optional) profile ref — mutually exclusive with provider/model
463
481
  # provider: claude # (string, default: "claude")
@@ -477,6 +495,8 @@ providers:
477
495
  dangerously_skip_permissions: true # (bool, default: false)
478
496
  allowed_tools: [] # (list of strings, default: []) tool allowlist
479
497
  disallowed_tools: [] # (list of strings, default: []) tool denylist
498
+ system_prompt: "Custom system prompt" # (string, optional) override the default system prompt
499
+ append_system_prompt: "Extra instructions" # (string, optional) append to the default system prompt
480
500
  inactivity_timeout: 600 # (int, optional) seconds before killing inactive subprocess
481
501
 
482
502
  codex:
@@ -519,27 +539,31 @@ hooks:
519
539
  | Flag | Description |
520
540
  |------|-------------|
521
541
  | `--version` | Show version and exit |
522
- | `--ci` | Run in CI mode (non-interactive) |
523
- | `--interactive` | Force interactive mode |
542
+ | `--ci` | Run in CI mode (non-interactive, mutually exclusive with `--interactive`). Also auto-detected from `CI` and `GITHUB_ACTIONS` environment variables |
543
+ | `--interactive` | Force interactive mode (mutually exclusive with `--ci`) |
524
544
 
525
545
  ### Commands
526
546
 
527
547
  | Command | Description |
528
548
  |---------|-------------|
549
+ | `fdsx init` | Initialize a new fdsx project with interactive setup |
550
+ | `fdsx init --skill` | Install the /fdsx Claude Code skill only (skip scaffold) |
551
+ | `fdsx run` | Execute tasks from default tasks directory (`default_tasks_dir` or `.fdsx/tasks/`) |
529
552
  | `fdsx run <workflow.yaml>` | Execute a workflow |
530
553
  | `fdsx run <workflow.yaml> --input key=value` | Pass input variables |
531
- | `fdsx run <workflow.yaml> --tasks tasks.yaml` | In-memory batch execution |
532
554
  | `fdsx run --tasks-dir <dir>` | Persistent batch execution (workflow optional) |
533
555
  | `fdsx run ... --quiet` | Suppress stderr streaming output |
534
556
  | `fdsx run ... --auto-workflow` | Skip workflow confirmation UI |
535
- | `fdsx run ... --confirm-workflow` | Show workflow confirmation UI |
557
+ | `fdsx run ... --confirm-workflow` | Show workflow confirmation UI (requires interactive mode) |
558
+ | `fdsx run ... --continue-on-error` | Continue processing remaining entries on error in tasks-dir mode |
536
559
  | `fdsx resume --thread-id <id>` | Resume from checkpoint |
537
560
  | `fdsx resume --thread-id <id> --base-dir <dir>` | Resume with custom base directory |
538
561
  | `fdsx validate <workflow.yaml>` | Validate YAML syntax |
539
562
  | `fdsx list` | List recent runs |
540
563
  | `fdsx list --base-dir <dir>` | List runs from custom base directory |
541
- | `fdsx split <task_file>` | Split a task file into individual task files |
542
- | `fdsx split <task_file> --force` | Clear existing tasks directory before splitting |
564
+ | `fdsx add <task_file>` | Add a task file to the batch execution queue (single task) |
565
+ | `fdsx add <task_file> --split` | Split a task file into individual task files |
566
+ | `fdsx add <task_file> --split --force` | Clear existing tasks directory before splitting |
543
567
 
544
568
  ## Example Workflow
545
569
 
@@ -610,8 +634,8 @@ states:
610
634
 
611
635
  Run this example:
612
636
  ```bash
613
- # First run in a new directory scaffolds .fdsx/ with example workflows:
614
- fdsx run
637
+ # Initialize the project (creates .fdsx/ with config and example workflows):
638
+ fdsx init
615
639
 
616
640
  # Then run the scaffolded example workflow:
617
641
  fdsx run .fdsx/workflows/plan-implement-review/workflow.yaml --input task="Build a web calculator"
@@ -10,9 +10,11 @@ fdsx enables you to define AI agent workflows in YAML, combining the durability
10
10
 
11
11
  **Key features:**
12
12
  - Declarative YAML-based workflow definition
13
+ - Interactive project initialization and scaffolding
13
14
  - Stateful execution with checkpoint/resume
14
15
  - Parallel execution with branch aggregation
15
- - Batch task processing (in-memory and persistent)
16
+ - Map state for iterating over arrays with sub-workflows
17
+ - Persistent batch task processing with crash-resilient resume
16
18
  - Multiple LLM provider support (Claude, Codex, Gemini, OpenCode, and system commands)
17
19
  - Named profiles for reusable provider/model configuration
18
20
  - Webhook notifications on wait states
@@ -34,6 +36,14 @@ uv tool install fdsx
34
36
 
35
37
  ## Quick Start
36
38
 
39
+ Initialize a new project:
40
+
41
+ ```bash
42
+ fdsx init
43
+ ```
44
+
45
+ This interactively scaffolds a `.fdsx/` directory with configuration and example workflows.
46
+
37
47
  Create a simple YAML workflow file:
38
48
 
39
49
  ```yaml
@@ -144,6 +154,8 @@ states:
144
154
  type: llm_classify # (literal, REQUIRED) only "llm_classify" supported
145
155
  provider: claude # (string, REQUIRED) LLM provider for classification
146
156
  prompt: "Classify as APPROVED or NEEDS_FIX" # (string, REQUIRED)
157
+ # Alternatively, use a profile reference (mutually exclusive with provider):
158
+ # profile: smarty
147
159
 
148
160
  # --- Execution control ---
149
161
  retry: 3 # (int, default: 3) retry attempts on failure
@@ -194,8 +206,10 @@ states:
194
206
  parallel_review:
195
207
  type: parallel # (REQUIRED) literal "parallel"
196
208
  branches: # (list, REQUIRED) each branch is an independent execution
197
- - provider: claude # same provider rules as task (or use profile:)
209
+ - provider: claude # same provider rules as task
198
210
  model: claude-sonnet-4-6
211
+ # Alternatively, use a profile reference (mutually exclusive with provider/model):
212
+ # profile: smarty
199
213
  prompt_template: |
200
214
  Review code quality: {implementation}
201
215
  # prompt_file: review.md # alternative to prompt_template
@@ -246,7 +260,7 @@ states:
246
260
  result_path: $.iter.step2
247
261
  retry: 0
248
262
  fail_fast: true # (bool, default: true) stop all iterations on first failure
249
- result_path: $.map_results # (string, optional) JSONPath for the results array
263
+ result_path: $.map_results # (string, REQUIRED) JSONPath for the results array
250
264
  max_iterations: 10 # (int, optional) max times this state can be re-entered
251
265
  hooks: # (optional)
252
266
  next: after_map # next / end — same rules as task
@@ -406,6 +420,10 @@ workflows_dir: .fdsx/workflows # (string, default: ".fdsx/workflows")
406
420
  # must be relative, no ".." components
407
421
  # where `fdsx run --tasks-dir` discovers workflows
408
422
 
423
+ # --- Default tasks directory ---
424
+ default_tasks_dir: .fdsx/tasks # (string, optional) default directory for bare `fdsx run`
425
+ # when no workflow, --tasks, or --tasks-dir is given
426
+
409
427
  # --- Auto-workflow selection ---
410
428
  auto_workflow: false # (bool, default: false) skip interactive confirmation UI
411
429
 
@@ -417,7 +435,7 @@ workflow_selector:
417
435
  extra_instructions: | # (string, optional) appended to the selection prompt
418
436
  Prefer simple-impl for small tasks.
419
437
 
420
- # --- Task splitter: LLM used by `fdsx split` ---
438
+ # --- Task splitter: LLM used by `fdsx add --split` ---
421
439
  task_splitter:
422
440
  profile: smarty # (string, optional) profile ref — mutually exclusive with provider/model
423
441
  # provider: claude # (string, default: "claude")
@@ -437,6 +455,8 @@ providers:
437
455
  dangerously_skip_permissions: true # (bool, default: false)
438
456
  allowed_tools: [] # (list of strings, default: []) tool allowlist
439
457
  disallowed_tools: [] # (list of strings, default: []) tool denylist
458
+ system_prompt: "Custom system prompt" # (string, optional) override the default system prompt
459
+ append_system_prompt: "Extra instructions" # (string, optional) append to the default system prompt
440
460
  inactivity_timeout: 600 # (int, optional) seconds before killing inactive subprocess
441
461
 
442
462
  codex:
@@ -479,27 +499,31 @@ hooks:
479
499
  | Flag | Description |
480
500
  |------|-------------|
481
501
  | `--version` | Show version and exit |
482
- | `--ci` | Run in CI mode (non-interactive) |
483
- | `--interactive` | Force interactive mode |
502
+ | `--ci` | Run in CI mode (non-interactive, mutually exclusive with `--interactive`). Also auto-detected from `CI` and `GITHUB_ACTIONS` environment variables |
503
+ | `--interactive` | Force interactive mode (mutually exclusive with `--ci`) |
484
504
 
485
505
  ### Commands
486
506
 
487
507
  | Command | Description |
488
508
  |---------|-------------|
509
+ | `fdsx init` | Initialize a new fdsx project with interactive setup |
510
+ | `fdsx init --skill` | Install the /fdsx Claude Code skill only (skip scaffold) |
511
+ | `fdsx run` | Execute tasks from default tasks directory (`default_tasks_dir` or `.fdsx/tasks/`) |
489
512
  | `fdsx run <workflow.yaml>` | Execute a workflow |
490
513
  | `fdsx run <workflow.yaml> --input key=value` | Pass input variables |
491
- | `fdsx run <workflow.yaml> --tasks tasks.yaml` | In-memory batch execution |
492
514
  | `fdsx run --tasks-dir <dir>` | Persistent batch execution (workflow optional) |
493
515
  | `fdsx run ... --quiet` | Suppress stderr streaming output |
494
516
  | `fdsx run ... --auto-workflow` | Skip workflow confirmation UI |
495
- | `fdsx run ... --confirm-workflow` | Show workflow confirmation UI |
517
+ | `fdsx run ... --confirm-workflow` | Show workflow confirmation UI (requires interactive mode) |
518
+ | `fdsx run ... --continue-on-error` | Continue processing remaining entries on error in tasks-dir mode |
496
519
  | `fdsx resume --thread-id <id>` | Resume from checkpoint |
497
520
  | `fdsx resume --thread-id <id> --base-dir <dir>` | Resume with custom base directory |
498
521
  | `fdsx validate <workflow.yaml>` | Validate YAML syntax |
499
522
  | `fdsx list` | List recent runs |
500
523
  | `fdsx list --base-dir <dir>` | List runs from custom base directory |
501
- | `fdsx split <task_file>` | Split a task file into individual task files |
502
- | `fdsx split <task_file> --force` | Clear existing tasks directory before splitting |
524
+ | `fdsx add <task_file>` | Add a task file to the batch execution queue (single task) |
525
+ | `fdsx add <task_file> --split` | Split a task file into individual task files |
526
+ | `fdsx add <task_file> --split --force` | Clear existing tasks directory before splitting |
503
527
 
504
528
  ## Example Workflow
505
529
 
@@ -570,8 +594,8 @@ states:
570
594
 
571
595
  Run this example:
572
596
  ```bash
573
- # First run in a new directory scaffolds .fdsx/ with example workflows:
574
- fdsx run
597
+ # Initialize the project (creates .fdsx/ with config and example workflows):
598
+ fdsx init
575
599
 
576
600
  # Then run the scaffolded example workflow:
577
601
  fdsx run .fdsx/workflows/plan-implement-review/workflow.yaml --input task="Build a web calculator"
@@ -592,4 +616,4 @@ fdsx list
592
616
 
593
617
  ## License
594
618
 
595
- MIT License.
619
+ MIT License.
@@ -1,3 +1,4 @@
1
+ import os
1
2
  import sys
2
3
  from pathlib import Path
3
4
 
@@ -33,14 +34,13 @@ from fdsx.core.init import (
33
34
  needs_init,
34
35
  scaffold,
35
36
  )
37
+ from fdsx.core.mode import is_interactive, set_interactive_mode
36
38
  from fdsx.core.thread_id import generate_thread_id
37
39
  from fdsx.display.terminal import Spinner, _sanitize_output, display_resume_command
38
40
  from fdsx.models.init import InitConfig
39
41
 
40
42
  app = typer.Typer(help="fdsx - Declarative AI agent workflow execution framework")
41
43
 
42
- _interactive_mode: bool | None = None
43
-
44
44
 
45
45
  def _validate_tasks_dir(tasks_dir: Path) -> None:
46
46
  if not tasks_dir.exists():
@@ -82,7 +82,6 @@ def main(
82
82
  help="Run in interactive mode (enables TTY detection if not explicitly set).",
83
83
  ),
84
84
  ) -> None:
85
- global _interactive_mode
86
85
  if ci and interactive:
87
86
  typer.echo(
88
87
  "Error: --ci and --interactive are mutually exclusive",
@@ -90,11 +89,16 @@ def main(
90
89
  )
91
90
  raise typer.Exit(code=2)
92
91
  if interactive:
93
- _interactive_mode = True
92
+ set_interactive_mode(True)
94
93
  elif ci:
95
- _interactive_mode = False
94
+ set_interactive_mode(False)
96
95
  else:
97
- _interactive_mode = sys.stdin.isatty()
96
+ ci_env = os.environ.get("CI", "").lower() in ("true", "1", "yes")
97
+ gh_actions = os.environ.get("GITHUB_ACTIONS", "").lower() == "true"
98
+ if ci_env or gh_actions:
99
+ set_interactive_mode(False)
100
+ else:
101
+ set_interactive_mode(sys.stdin.isatty())
98
102
  if ctx.invoked_subcommand != "init" and needs_init(Path.cwd()):
99
103
  typer.echo(
100
104
  "No .fdsx/ directory found. Run 'fdsx init' to set up your project.",
@@ -102,7 +106,7 @@ def main(
102
106
  )
103
107
  raise typer.Exit(code=0)
104
108
  elif (
105
- _interactive_mode
109
+ is_interactive()
106
110
  and not needs_init(Path.cwd())
107
111
  and not (Path.cwd() / ".fdsx" / ".gitignore").exists()
108
112
  ):
@@ -122,11 +126,6 @@ def run(
122
126
  input_vars: list[str] | None = typer.Option(
123
127
  None, "--input", help="Input variable as KEY=VALUE"
124
128
  ),
125
- tasks_file: Path | None = typer.Option(
126
- None,
127
- "--tasks",
128
- help="Batch task file for in-memory splitting and execution (requires workflow argument)",
129
- ),
130
129
  tasks_dir: Path | None = typer.Option(
131
130
  None,
132
131
  "--tasks-dir",
@@ -147,8 +146,13 @@ def run(
147
146
  "--quiet",
148
147
  help="Suppress stderr streaming output from providers. Log files are still written and completion summary is still shown.",
149
148
  ),
149
+ continue_on_error: bool = typer.Option(
150
+ False,
151
+ "--continue-on-error",
152
+ help="Continue processing remaining entries when an error occurs in tasks-dir mode.",
153
+ ),
150
154
  ) -> None:
151
- """Run a workflow. Supports single execution, in-memory batch (--tasks), and persistent batch (--tasks-dir) modes.
155
+ """Run a workflow. Supports single execution and persistent batch (--tasks-dir) modes.
152
156
 
153
157
  Shows an animated spinner during workflow auto-selection for tasks-dir mode.
154
158
  Displays an interactive numbered-list CUI for workflow confirmation (in interactive terminals).
@@ -156,28 +160,20 @@ def run(
156
160
  In non-interactive (non-TTY) terminals, auto-confirms without prompting."""
157
161
  config = load_config()
158
162
  if tasks_dir is not None:
159
- if input_vars is not None or tasks_file is not None:
163
+ if input_vars is not None:
160
164
  typer.echo(
161
- "Error: --tasks-dir is mutually exclusive with --input and --tasks",
165
+ "Error: --tasks-dir is mutually exclusive with --input",
162
166
  err=True,
163
167
  )
164
168
  raise typer.Exit(code=2)
165
169
  _validate_tasks_dir(tasks_dir)
166
- elif input_vars and tasks_file is not None:
170
+ elif input_vars is not None and workflow is None:
167
171
  typer.echo(
168
- "Error: --input and --tasks are mutually exclusive",
172
+ "Error: workflow argument is required when using --input",
169
173
  err=True,
170
174
  )
171
175
  raise typer.Exit(code=2)
172
- elif tasks_file is not None and workflow is None:
173
- typer.echo(
174
- "Error: workflow argument is required when using --tasks",
175
- err=True,
176
- )
177
- raise typer.Exit(code=2)
178
- elif (
179
- workflow is None and tasks_dir is None and tasks_file is None and not input_vars
180
- ):
176
+ elif workflow is None and tasks_dir is None and not input_vars:
181
177
  resolved_tasks_dir = Path(
182
178
  config.default_tasks_dir if config.default_tasks_dir else ".fdsx/tasks/"
183
179
  ).expanduser()
@@ -191,6 +187,13 @@ def run(
191
187
  )
192
188
  raise typer.Exit(code=2)
193
189
 
190
+ if confirm_workflow and not is_interactive():
191
+ typer.echo(
192
+ "Error: --confirm-workflow requires interactive mode and cannot be used with --ci or in CI environments",
193
+ err=True,
194
+ )
195
+ raise typer.Exit(code=2)
196
+
194
197
  inputs = None
195
198
  if input_vars:
196
199
  inputs = {}
@@ -222,20 +225,13 @@ def run(
222
225
  base_dir,
223
226
  auto_workflow=effective_auto_workflow,
224
227
  quiet=quiet,
228
+ continue_on_error=continue_on_error,
225
229
  )
226
230
  has_failure = any(r.get("status") == "failed" for r in results)
227
231
  if has_failure:
228
232
  raise typer.Exit(code=1)
229
233
  else:
230
234
  raise typer.Exit(code=0)
231
- elif tasks_file is not None:
232
- assert workflow is not None
233
- results = engine.run_batch(workflow, tasks_file, base_dir, quiet=quiet)
234
- has_failure = any(r.get("status") == "failed" for r in results)
235
- if has_failure:
236
- raise typer.Exit(code=1)
237
- else:
238
- raise typer.Exit(code=0)
239
235
  else:
240
236
  assert workflow is not None
241
237
  if current_thread_id is None:
@@ -368,7 +364,7 @@ def _run_skill_only_install() -> None:
368
364
 
369
365
  def _prompt_and_install_skill(cwd: Path) -> None:
370
366
  """Prompt for skill install after scaffold completes. Handles decline gracefully."""
371
- if not _interactive_mode:
367
+ if not is_interactive():
372
368
  return
373
369
 
374
370
  try:
@@ -10,7 +10,6 @@ import structlog
10
10
 
11
11
  from fdsx.core.config import TaskSplitterConfig
12
12
  from fdsx.display.terminal import _sanitize_output
13
- from fdsx.models.flow import Flow
14
13
  from fdsx.models.task import (
15
14
  TaskEntry,
16
15
  TaskFile,
@@ -36,47 +35,6 @@ def _slugify(text: str, max_length: int = 40) -> str:
36
35
  return slug or "task"
37
36
 
38
37
 
39
- def split_tasks(
40
- task_content: str, flow: Flow, task_splitter: TaskSplitterConfig
41
- ) -> list[str]:
42
- """Invoke the task_splitter LLM to split the task file content into individual tasks.
43
-
44
- Args:
45
- task_content: The content of the task file
46
- flow: The flow definition
47
- task_splitter: The task splitter configuration
48
-
49
- Returns:
50
- List of task description strings
51
- """
52
- provider = get_provider(task_splitter.provider)
53
-
54
- state_names = list(flow.states.keys())
55
- input_vars = _extract_input_variables(flow)
56
-
57
- prompt = _build_task_split_prompt(
58
- task_content,
59
- state_names,
60
- input_vars,
61
- extra_instructions=task_splitter.extra_instructions,
62
- )
63
-
64
- result = provider.execute(
65
- prompt=prompt,
66
- model=task_splitter.model,
67
- )
68
-
69
- if result.exit_code != 0:
70
- raise RuntimeError(f"Task splitter failed: {result.stderr}")
71
-
72
- try:
73
- groups = _parse_structured_tasks(result.stdout)
74
- flattened = [entry.description for group in groups for entry in group]
75
- return flattened
76
- except ValueError:
77
- return _parse_task_list(result.stdout)
78
-
79
-
80
38
  def _invoke_splitter_and_parse(
81
39
  provider: "ProviderBase", prompt: str, model: str | None
82
40
  ) -> tuple[list[list[TaskEntry]], str]:
@@ -340,54 +298,6 @@ IMPORTANT: Output ONLY the JSON array, no additional text, explanations, or mark
340
298
  return prompt
341
299
 
342
300
 
343
- def _extract_input_variables(flow: Flow) -> set[str]:
344
- """Extract expected input variables from the flow.
345
-
346
- Scans prompt_template fields for {variable} references to identify actual inputs.
347
- Also includes 'task' as the standard batch input variable.
348
- """
349
- import re
350
-
351
- from fdsx.models.flow import ParallelState, TaskState
352
-
353
- input_vars: set[str] = {"task"}
354
- # Matches {var}, {var.field}, {var[0]} etc.
355
- var_pattern = r"\{(\w+(?:\.\w+)*(?:\[\d+\])?)\}"
356
-
357
- for _state_name, state in flow.states.items():
358
- if isinstance(state, TaskState) and state.prompt_template:
359
- for match in re.findall(var_pattern, state.prompt_template):
360
- root = match.split(".")[0].split("[")[0]
361
- input_vars.add(root)
362
- elif isinstance(state, ParallelState):
363
- for branch in state.branches:
364
- if branch.prompt_template:
365
- for match in re.findall(var_pattern, branch.prompt_template):
366
- root = match.split(".")[0].split("[")[0]
367
- input_vars.add(root)
368
-
369
- return input_vars
370
-
371
-
372
- def _parse_task_list(response: str) -> list[str]:
373
- """Parse the LLM response into a list of task strings."""
374
- tasks: list[str] = []
375
- lines = response.strip().split("\n")
376
-
377
- for line in lines:
378
- line = line.strip()
379
- if not line:
380
- continue
381
-
382
- if line[0].isdigit() and ". " in line:
383
- task = line.split(". ", 1)[1].strip()
384
- tasks.append(task)
385
- else:
386
- tasks.append(line)
387
-
388
- return tasks
389
-
390
-
391
301
  def _parse_structured_tasks(response: str) -> list[list[TaskEntry]]:
392
302
  """Parse the LLM JSON response into a list of file groups.
393
303
 
@@ -551,76 +461,6 @@ def move_task_to_completed(file_path: Path) -> None:
551
461
  shutil.move(str(file_path), str(dest))
552
462
 
553
463
 
554
- def display_task_list(tasks: list[str]) -> bool:
555
- """Display the split tasks in numbered format and prompt for confirmation.
556
-
557
- Args:
558
- tasks: List of task description strings
559
-
560
- Returns:
561
- True if user approves, False if rejected
562
- """
563
- print("The following tasks will be executed:", file=sys.stderr)
564
- print("-" * 60, file=sys.stderr)
565
-
566
- for i, task in enumerate(tasks, 1):
567
- task_preview = task[:70] + "..." if len(task) > 70 else task
568
- print(f" {i}. {_sanitize_output(task_preview)}", file=sys.stderr)
569
-
570
- print("-" * 60, file=sys.stderr)
571
-
572
- while True:
573
- response = input("Approve task list? (y/n): ").strip().lower()
574
- if response == "y":
575
- return True
576
- elif response == "n":
577
- return False
578
-
579
-
580
- def display_batch_summary(results: list[dict[str, Any]]) -> None:
581
- """Display a summary table of all task results.
582
-
583
- Args:
584
- results: List of result dicts with task_index, task_description, thread_id, status, error
585
- """
586
- print("\n" + "=" * 80, file=sys.stderr)
587
- print("BATCH EXECUTION SUMMARY", file=sys.stderr)
588
- print("=" * 80, file=sys.stderr)
589
- print(
590
- f"{'#':<4} {'STATUS':<12} {'THREAD_ID':<36} {'TASK':<25}",
591
- file=sys.stderr,
592
- )
593
- print("-" * 80, file=sys.stderr)
594
-
595
- for result in results:
596
- task_idx = result.get("task_index", 0) + 1
597
- status = result.get("status", "unknown")
598
- thread_id = result.get("thread_id", "")[:36]
599
- task_desc = result.get("task_description", "")[:25]
600
-
601
- status_symbol = "✓" if status == "completed" else "✗"
602
-
603
- print(
604
- f"{task_idx:<4} {status_symbol} {status:<10} {_sanitize_output(thread_id):<36} {_sanitize_output(task_desc):<25}",
605
- file=sys.stderr,
606
- )
607
-
608
- if result.get("error"):
609
- error_preview = result["error"][:60]
610
- print(f" Error: {_sanitize_output(error_preview)}", file=sys.stderr)
611
-
612
- print("-" * 80, file=sys.stderr)
613
-
614
- total = len(results)
615
- succeeded = sum(1 for r in results if r.get("status") == "completed")
616
- failed = total - succeeded
617
-
618
- print(
619
- f"Total: {total} | Succeeded: {succeeded} | Failed: {failed}", file=sys.stderr
620
- )
621
- print("=" * 80, file=sys.stderr)
622
-
623
-
624
464
  def display_tasks_dir_summary(results: list[dict[str, Any]]) -> None:
625
465
  """Display a summary of tasks-dir execution results.
626
466
 
@@ -5,7 +5,6 @@ existing imports like ``from fdsx.core.engine import run_flow`` continue
5
5
  to work without changes.
6
6
  """
7
7
 
8
- from .batch import run_batch
9
8
  from .results import (
10
9
  _calc_elapsed,
11
10
  _detect_abort_status,
@@ -36,7 +35,6 @@ __all__ = [
36
35
  "_workflow_persist_id",
37
36
  "load_tasks_dir",
38
37
  "resume_flow",
39
- "run_batch",
40
38
  "run_flow",
41
39
  "run_tasks_dir",
42
40
  "validate_flow",