ouroboros-ai 0.1.1__tar.gz → 0.2.0__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.

Potentially problematic release.


This version of ouroboros-ai might be problematic. Click here for more details.

Files changed (203) hide show
  1. ouroboros_ai-0.2.0/CHANGELOG.md +86 -0
  2. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/PKG-INFO +32 -2
  3. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/README.md +31 -1
  4. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/pyproject.toml +1 -1
  5. ouroboros_ai-0.2.0/src/ouroboros/__init__.py +28 -0
  6. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/src/ouroboros/bigbang/ambiguity.py +109 -48
  7. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/src/ouroboros/bigbang/interview.py +9 -4
  8. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/src/ouroboros/cli/commands/run.py +9 -1
  9. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/src/ouroboros/core/__init__.py +11 -0
  10. ouroboros_ai-0.2.0/src/ouroboros/core/security.py +327 -0
  11. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/src/ouroboros/observability/logging.py +70 -0
  12. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/src/ouroboros/providers/litellm_adapter.py +15 -1
  13. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/tests/unit/bigbang/test_ambiguity.py +148 -11
  14. ouroboros_ai-0.2.0/tests/unit/core/test_security.py +276 -0
  15. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/tests/unit/observability/test_logging.py +74 -0
  16. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/tests/unit/test_main_entry_point.py +1 -1
  17. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/uv.lock +1 -1
  18. ouroboros_ai-0.1.1/src/ouroboros/__init__.py +0 -15
  19. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/.gitignore +0 -0
  20. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/.pre-commit-config.yaml +0 -0
  21. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/.python-version +0 -0
  22. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/LICENSE +0 -0
  23. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/PR-43-CODE-REVIEW-REPORT.md +0 -0
  24. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/_bmad-output/implementation-artifacts/github-issue-mapping.yaml +0 -0
  25. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/_bmad-output/implementation-artifacts/sprint-status.yaml +0 -0
  26. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/_bmad-output/implementation-artifacts/stories/0-1-project-initialization-with-uv.md +0 -0
  27. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/_bmad-output/implementation-artifacts/stories/0-2-core-types-and-error-handling.md +0 -0
  28. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/_bmad-output/implementation-artifacts/stories/0-3-event-store-with-sqlalchemy-core.md +0 -0
  29. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/_bmad-output/implementation-artifacts/stories/0-4-configuration-and-credentials-management.md +0 -0
  30. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/_bmad-output/implementation-artifacts/stories/0-5-llm-provider-adapter-with-litellm.md +0 -0
  31. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/_bmad-output/implementation-artifacts/stories/0-6-cli-skeleton-with-typer-and-rich.md +0 -0
  32. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/_bmad-output/implementation-artifacts/stories/0-7-structured-logging-with-structlog.md +0 -0
  33. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/_bmad-output/implementation-artifacts/stories/0-8-checkpoint-and-recovery-system.md +0 -0
  34. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/_bmad-output/implementation-artifacts/stories/0-9-context-compression-engine.md +0 -0
  35. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/_bmad-output/implementation-artifacts/stories/1-1-interview-protocol-engine.md +0 -0
  36. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/_bmad-output/implementation-artifacts/stories/1-2-ambiguity-score-calculation.md +0 -0
  37. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/_bmad-output/implementation-artifacts/stories/1-3-immutable-seed-generation.md +0 -0
  38. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/_bmad-output/implementation-artifacts/stories/2-1-three-tier-model-configuration.md +0 -0
  39. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/_bmad-output/implementation-artifacts/stories/2-2-complexity-based-routing.md +0 -0
  40. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/_bmad-output/implementation-artifacts/stories/2-3-escalation-on-failure.md +0 -0
  41. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/_bmad-output/implementation-artifacts/stories/2-4-downgrade-on-success.md +0 -0
  42. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/_bmad-output/implementation-artifacts/stories/3-1-double-diamond-cycle-implementation.md +0 -0
  43. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/_bmad-output/implementation-artifacts/stories/3-2-hierarchical-ac-decomposition.md +0 -0
  44. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/_bmad-output/implementation-artifacts/stories/3-3-atomicity-detection.md +0 -0
  45. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/_bmad-output/implementation-artifacts/stories/3-4-subagent-isolation.md +0 -0
  46. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/_bmad-output/implementation-artifacts/stories/4-1-stagnation-detection-4-patterns.md +0 -0
  47. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/_bmad-output/implementation-artifacts/stories/4-2-lateral-thinking-personas.md +0 -0
  48. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/_bmad-output/implementation-artifacts/stories/4-3-persona-rotation-strategy.md +0 -0
  49. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/_bmad-output/implementation-artifacts/stories/5-1-stage-1-mechanical-verification.md +0 -0
  50. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/_bmad-output/implementation-artifacts/stories/5-2-stage-2-semantic-evaluation.md +0 -0
  51. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/_bmad-output/implementation-artifacts/stories/5-3-stage-3-multi-model-consensus.md +0 -0
  52. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/_bmad-output/implementation-artifacts/stories/5-4-consensus-trigger-matrix.md +0 -0
  53. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/_bmad-output/implementation-artifacts/stories/6-1-drift-measurement-engine.md +0 -0
  54. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/_bmad-output/implementation-artifacts/stories/6-2-automatic-retrospective.md +0 -0
  55. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/_bmad-output/implementation-artifacts/stories/7-1-todo-registry.md +0 -0
  56. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/_bmad-output/implementation-artifacts/stories/7-2-secondary-loop-batch-processing.md +0 -0
  57. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/_bmad-output/planning-artifacts/architecture.md +0 -0
  58. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/_bmad-output/planning-artifacts/bmm-workflow-status.yaml +0 -0
  59. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/_bmad-output/planning-artifacts/epics.md +0 -0
  60. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/_bmad-output/update-stories.sh +0 -0
  61. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/docs/running-with-claude-code.md +0 -0
  62. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/project-context.md +0 -0
  63. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/requirement/1_EXECUTIVE_SUMMARY.md +0 -0
  64. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/requirement/2_FULL_SPECIFICATION.md +0 -0
  65. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/requirement/3_CONFIG_TEMPLATE.yaml +0 -0
  66. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/requirement/4_REDDIT_EXAMPLE.md +0 -0
  67. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/src/ouroboros/__main__.py +0 -0
  68. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/src/ouroboros/bigbang/__init__.py +0 -0
  69. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/src/ouroboros/bigbang/seed_generator.py +0 -0
  70. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/src/ouroboros/cli/__init__.py +0 -0
  71. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/src/ouroboros/cli/commands/__init__.py +0 -0
  72. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/src/ouroboros/cli/commands/config.py +0 -0
  73. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/src/ouroboros/cli/commands/init.py +0 -0
  74. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/src/ouroboros/cli/commands/status.py +0 -0
  75. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/src/ouroboros/cli/formatters/__init__.py +0 -0
  76. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/src/ouroboros/cli/formatters/panels.py +0 -0
  77. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/src/ouroboros/cli/formatters/progress.py +0 -0
  78. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/src/ouroboros/cli/formatters/tables.py +0 -0
  79. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/src/ouroboros/cli/main.py +0 -0
  80. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/src/ouroboros/config/__init__.py +0 -0
  81. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/src/ouroboros/config/loader.py +0 -0
  82. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/src/ouroboros/config/models.py +0 -0
  83. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/src/ouroboros/core/ac_tree.py +0 -0
  84. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/src/ouroboros/core/context.py +0 -0
  85. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/src/ouroboros/core/errors.py +0 -0
  86. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/src/ouroboros/core/seed.py +0 -0
  87. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/src/ouroboros/core/types.py +0 -0
  88. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/src/ouroboros/evaluation/__init__.py +0 -0
  89. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/src/ouroboros/evaluation/consensus.py +0 -0
  90. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/src/ouroboros/evaluation/mechanical.py +0 -0
  91. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/src/ouroboros/evaluation/models.py +0 -0
  92. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/src/ouroboros/evaluation/pipeline.py +0 -0
  93. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/src/ouroboros/evaluation/semantic.py +0 -0
  94. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/src/ouroboros/evaluation/trigger.py +0 -0
  95. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/src/ouroboros/events/__init__.py +0 -0
  96. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/src/ouroboros/events/base.py +0 -0
  97. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/src/ouroboros/events/decomposition.py +0 -0
  98. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/src/ouroboros/events/evaluation.py +0 -0
  99. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/src/ouroboros/execution/__init__.py +0 -0
  100. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/src/ouroboros/execution/atomicity.py +0 -0
  101. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/src/ouroboros/execution/decomposition.py +0 -0
  102. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/src/ouroboros/execution/double_diamond.py +0 -0
  103. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/src/ouroboros/execution/subagent.py +0 -0
  104. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/src/ouroboros/observability/__init__.py +0 -0
  105. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/src/ouroboros/observability/drift.py +0 -0
  106. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/src/ouroboros/observability/retrospective.py +0 -0
  107. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/src/ouroboros/orchestrator/__init__.py +0 -0
  108. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/src/ouroboros/orchestrator/adapter.py +0 -0
  109. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/src/ouroboros/orchestrator/events.py +0 -0
  110. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/src/ouroboros/orchestrator/runner.py +0 -0
  111. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/src/ouroboros/orchestrator/session.py +0 -0
  112. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/src/ouroboros/persistence/__init__.py +0 -0
  113. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/src/ouroboros/persistence/checkpoint.py +0 -0
  114. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/src/ouroboros/persistence/event_store.py +0 -0
  115. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/src/ouroboros/persistence/migrations/__init__.py +0 -0
  116. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/src/ouroboros/persistence/migrations/runner.py +0 -0
  117. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/src/ouroboros/persistence/migrations/scripts/001_initial.sql +0 -0
  118. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/src/ouroboros/persistence/schema.py +0 -0
  119. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/src/ouroboros/persistence/uow.py +0 -0
  120. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/src/ouroboros/providers/__init__.py +0 -0
  121. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/src/ouroboros/providers/base.py +0 -0
  122. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/src/ouroboros/providers/claude_code_adapter.py +0 -0
  123. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/src/ouroboros/py.typed +0 -0
  124. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/src/ouroboros/resilience/__init__.py +0 -0
  125. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/src/ouroboros/resilience/lateral.py +0 -0
  126. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/src/ouroboros/resilience/stagnation.py +0 -0
  127. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/src/ouroboros/routing/__init__.py +0 -0
  128. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/src/ouroboros/routing/complexity.py +0 -0
  129. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/src/ouroboros/routing/downgrade.py +0 -0
  130. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/src/ouroboros/routing/escalation.py +0 -0
  131. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/src/ouroboros/routing/router.py +0 -0
  132. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/src/ouroboros/routing/tiers.py +0 -0
  133. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/src/ouroboros/secondary/__init__.py +0 -0
  134. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/src/ouroboros/secondary/scheduler.py +0 -0
  135. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/src/ouroboros/secondary/todo_registry.py +0 -0
  136. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/tests/conftest.py +0 -0
  137. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/tests/integration/test_entry_point.py +0 -0
  138. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/tests/unit/bigbang/__init__.py +0 -0
  139. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/tests/unit/bigbang/test_interview.py +0 -0
  140. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/tests/unit/bigbang/test_seed_generator.py +0 -0
  141. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/tests/unit/cli/__init__.py +0 -0
  142. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/tests/unit/cli/formatters/__init__.py +0 -0
  143. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/tests/unit/cli/formatters/test_console.py +0 -0
  144. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/tests/unit/cli/formatters/test_panels.py +0 -0
  145. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/tests/unit/cli/formatters/test_progress.py +0 -0
  146. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/tests/unit/cli/formatters/test_tables.py +0 -0
  147. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/tests/unit/cli/test_main.py +0 -0
  148. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/tests/unit/config/__init__.py +0 -0
  149. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/tests/unit/config/test_loader.py +0 -0
  150. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/tests/unit/config/test_models.py +0 -0
  151. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/tests/unit/core/__init__.py +0 -0
  152. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/tests/unit/core/test_ac_tree.py +0 -0
  153. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/tests/unit/core/test_context.py +0 -0
  154. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/tests/unit/core/test_errors.py +0 -0
  155. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/tests/unit/core/test_seed.py +0 -0
  156. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/tests/unit/core/test_types.py +0 -0
  157. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/tests/unit/evaluation/__init__.py +0 -0
  158. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/tests/unit/evaluation/test_consensus.py +0 -0
  159. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/tests/unit/evaluation/test_mechanical.py +0 -0
  160. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/tests/unit/evaluation/test_models.py +0 -0
  161. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/tests/unit/evaluation/test_semantic.py +0 -0
  162. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/tests/unit/evaluation/test_trigger.py +0 -0
  163. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/tests/unit/events/__init__.py +0 -0
  164. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/tests/unit/events/test_base.py +0 -0
  165. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/tests/unit/events/test_decomposition_events.py +0 -0
  166. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/tests/unit/execution/__init__.py +0 -0
  167. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/tests/unit/execution/test_atomicity.py +0 -0
  168. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/tests/unit/execution/test_decomposition.py +0 -0
  169. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/tests/unit/execution/test_double_diamond.py +0 -0
  170. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/tests/unit/execution/test_subagent_isolation.py +0 -0
  171. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/tests/unit/observability/__init__.py +0 -0
  172. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/tests/unit/observability/test_drift.py +0 -0
  173. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/tests/unit/observability/test_retrospective.py +0 -0
  174. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/tests/unit/orchestrator/__init__.py +0 -0
  175. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/tests/unit/orchestrator/test_adapter.py +0 -0
  176. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/tests/unit/orchestrator/test_events.py +0 -0
  177. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/tests/unit/orchestrator/test_runner.py +0 -0
  178. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/tests/unit/orchestrator/test_session.py +0 -0
  179. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/tests/unit/persistence/__init__.py +0 -0
  180. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/tests/unit/persistence/test_checkpoint.py +0 -0
  181. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/tests/unit/persistence/test_event_store.py +0 -0
  182. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/tests/unit/persistence/test_schema.py +0 -0
  183. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/tests/unit/persistence/test_uow.py +0 -0
  184. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/tests/unit/providers/__init__.py +0 -0
  185. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/tests/unit/providers/test_base.py +0 -0
  186. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/tests/unit/providers/test_litellm_adapter.py +0 -0
  187. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/tests/unit/resilience/__init__.py +0 -0
  188. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/tests/unit/resilience/test_lateral.py +0 -0
  189. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/tests/unit/resilience/test_stagnation.py +0 -0
  190. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/tests/unit/routing/__init__.py +0 -0
  191. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/tests/unit/routing/test_complexity.py +0 -0
  192. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/tests/unit/routing/test_downgrade.py +0 -0
  193. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/tests/unit/routing/test_escalation.py +0 -0
  194. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/tests/unit/routing/test_router.py +0 -0
  195. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/tests/unit/routing/test_tiers.py +0 -0
  196. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/tests/unit/secondary/__init__.py +0 -0
  197. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/tests/unit/secondary/test_scheduler.py +0 -0
  198. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/tests/unit/secondary/test_todo_registry.py +0 -0
  199. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/tests/unit/test_dependencies_configured.py +0 -0
  200. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/tests/unit/test_module_structure.py +0 -0
  201. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/tests/unit/test_project_initialization.py +0 -0
  202. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/tests/unit/test_tooling_configuration.py +0 -0
  203. {ouroboros_ai-0.1.1 → ouroboros_ai-0.2.0}/tools/sync_github_project.py +0 -0
@@ -0,0 +1,86 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.2.0] - 2026-01-27
9
+
10
+ ### Added
11
+
12
+ #### Security Module (`ouroboros.core.security`)
13
+ - New security utilities module with comprehensive protection features
14
+ - **API Key Management**
15
+ - `mask_api_key()` - Safely mask API keys for logging (shows only last 4 chars)
16
+ - `validate_api_key_format()` - Basic format validation for API keys
17
+ - **Sensitive Data Detection**
18
+ - `is_sensitive_field()` - Detect sensitive field names (api_key, password, token, etc.)
19
+ - `is_sensitive_value()` - Detect values that look like secrets
20
+ - `mask_sensitive_value()` - Mask potentially sensitive values
21
+ - `sanitize_for_logging()` - Create sanitized copies of dicts for safe logging
22
+ - **Input Validation**
23
+ - `InputValidator` class with size limits for DoS prevention:
24
+ - `MAX_INITIAL_CONTEXT_LENGTH` = 50KB
25
+ - `MAX_USER_RESPONSE_LENGTH` = 10KB
26
+ - `MAX_SEED_FILE_SIZE` = 1MB
27
+ - `MAX_LLM_RESPONSE_LENGTH` = 100KB
28
+
29
+ #### Logging Security
30
+ - Automatic sensitive data masking in structlog processor chain
31
+ - API keys, passwords, tokens are now automatically redacted in all log outputs
32
+ - Nested dictionaries are recursively sanitized
33
+ - Pattern-based detection for values starting with `sk-`, `pk-`, `Bearer`, etc.
34
+
35
+ ### Changed
36
+
37
+ #### Interview Engine
38
+ - Input validation now uses `InputValidator` for consistent size limits
39
+ - `start_interview()` validates initial context length
40
+ - `record_response()` validates user response length
41
+
42
+ #### LiteLLM Adapter
43
+ - LLM responses are now validated and truncated if exceeding size limits
44
+ - Warning logged when response truncation occurs
45
+
46
+ #### CLI Run Command
47
+ - Seed file size is now validated before loading
48
+ - Protection against oversized seed files
49
+
50
+ ### Security
51
+
52
+ - **API Key Management**: Keys are masked in logs, showing only provider prefix and last 4 characters
53
+ - **Input Validation**: All external inputs have size limits to prevent DoS attacks
54
+ - **Log Sanitization**: Sensitive data is automatically masked in all log outputs
55
+ - **Credentials Protection**: `credentials.yaml` continues to use chmod 600 permissions
56
+
57
+ ### Tests
58
+
59
+ - Added comprehensive test suite for security module (39 tests)
60
+ - Added sensitive data masking tests for logging module (5 tests)
61
+ - All 1341 tests passing
62
+
63
+ ## [0.1.1] - 2026-01-15
64
+
65
+ ### Added
66
+ - Initial release with core Ouroboros workflow system
67
+ - Big Bang (Phase 0) - Interview and Seed generation
68
+ - PAL Router (Phase 1) - Progressive Adaptive LLM selection
69
+ - Double Diamond (Phase 2) - Execution engine
70
+ - Resilience (Phase 3) - Stagnation detection and lateral thinking
71
+ - Evaluation (Phase 4) - Mechanical, semantic, and consensus evaluation
72
+ - Secondary Loop (Phase 5) - TODO registry and batch scheduler
73
+ - Orchestrator (Epic 8) - Claude Agent SDK integration
74
+ - CLI interface with Typer
75
+ - Event sourcing with SQLite persistence
76
+ - Structured logging with structlog
77
+
78
+ ### Fixed
79
+ - Various bug fixes and stability improvements
80
+
81
+ ## [0.1.0] - 2026-01-01
82
+
83
+ ### Added
84
+ - Initial project structure
85
+ - Core types and error hierarchy
86
+ - Basic configuration system
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ouroboros-ai
3
- Version: 0.1.1
3
+ Version: 0.2.0
4
4
  Summary: Self-Improving AI Workflow System
5
5
  Author-email: Q00 <jqyu.lee@gmail.com>
6
6
  License-File: LICENSE
@@ -52,7 +52,7 @@ Description-Content-Type: text/markdown
52
52
  <br/>
53
53
 
54
54
  <p align="center">
55
- <code>73 modules</code> · <code>1,292 tests</code> · <code>97%+ coverage</code>
55
+ <code>74 modules</code> · <code>1,341 tests</code> · <code>97%+ coverage</code>
56
56
  </p>
57
57
 
58
58
  <br/>
@@ -506,6 +506,36 @@ uv run ouroboros status health
506
506
 
507
507
  <br/>
508
508
 
509
+ ## ◈ Security
510
+
511
+ <br/>
512
+
513
+ Ouroboros includes built-in security features:
514
+
515
+ | Feature | Description |
516
+ |---------|-------------|
517
+ | **API Key Masking** | Keys are automatically masked in logs (`sk-...xxxx`) |
518
+ | **Log Sanitization** | Sensitive fields (password, token, secret) are redacted |
519
+ | **Input Validation** | Size limits prevent DoS attacks (50KB context, 1MB seed files) |
520
+ | **Credentials Protection** | `credentials.yaml` uses chmod 600 permissions |
521
+
522
+ ```python
523
+ from ouroboros.core import mask_api_key, sanitize_for_logging
524
+
525
+ # Mask API keys for display
526
+ masked = mask_api_key("sk-1234567890abcdef") # "sk-...cdef"
527
+
528
+ # Sanitize dicts before logging
529
+ safe_data = sanitize_for_logging({"api_key": "sk-secret", "name": "test"})
530
+ # {"api_key": "<REDACTED>", "name": "test"}
531
+ ```
532
+
533
+ <br/>
534
+
535
+ ---
536
+
537
+ <br/>
538
+
509
539
  ## ◈ Development
510
540
 
511
541
  <br/>
@@ -32,7 +32,7 @@
32
32
  <br/>
33
33
 
34
34
  <p align="center">
35
- <code>73 modules</code> · <code>1,292 tests</code> · <code>97%+ coverage</code>
35
+ <code>74 modules</code> · <code>1,341 tests</code> · <code>97%+ coverage</code>
36
36
  </p>
37
37
 
38
38
  <br/>
@@ -486,6 +486,36 @@ uv run ouroboros status health
486
486
 
487
487
  <br/>
488
488
 
489
+ ## ◈ Security
490
+
491
+ <br/>
492
+
493
+ Ouroboros includes built-in security features:
494
+
495
+ | Feature | Description |
496
+ |---------|-------------|
497
+ | **API Key Masking** | Keys are automatically masked in logs (`sk-...xxxx`) |
498
+ | **Log Sanitization** | Sensitive fields (password, token, secret) are redacted |
499
+ | **Input Validation** | Size limits prevent DoS attacks (50KB context, 1MB seed files) |
500
+ | **Credentials Protection** | `credentials.yaml` uses chmod 600 permissions |
501
+
502
+ ```python
503
+ from ouroboros.core import mask_api_key, sanitize_for_logging
504
+
505
+ # Mask API keys for display
506
+ masked = mask_api_key("sk-1234567890abcdef") # "sk-...cdef"
507
+
508
+ # Sanitize dicts before logging
509
+ safe_data = sanitize_for_logging({"api_key": "sk-secret", "name": "test"})
510
+ # {"api_key": "<REDACTED>", "name": "test"}
511
+ ```
512
+
513
+ <br/>
514
+
515
+ ---
516
+
517
+ <br/>
518
+
489
519
  ## ◈ Development
490
520
 
491
521
  <br/>
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "ouroboros-ai"
3
- version = "0.1.1"
3
+ version = "0.2.0"
4
4
  description = "Self-Improving AI Workflow System"
5
5
  readme = "README.md"
6
6
  authors = [
@@ -0,0 +1,28 @@
1
+ """Ouroboros - Self-Improving AI Workflow System.
2
+
3
+ A workflow system that uses Socratic questioning and ontological analysis
4
+ to transform ambiguous requirements into executable specifications.
5
+
6
+ Example:
7
+ # Using CLI
8
+ ouroboros init start "I want to build a task management CLI"
9
+ ouroboros run workflow seed.yaml
10
+
11
+ # Using Python
12
+ from ouroboros.core import Result, ValidationError
13
+ from ouroboros.bigbang import InterviewEngine
14
+ """
15
+
16
+ __version__ = "0.2.0"
17
+
18
+ __all__ = ["__version__", "main"]
19
+
20
+
21
+ def main() -> None:
22
+ """Main entry point for the Ouroboros CLI.
23
+
24
+ This function invokes the Typer app from ouroboros.cli.main.
25
+ """
26
+ from ouroboros.cli.main import app
27
+
28
+ app()
@@ -36,6 +36,9 @@ DEFAULT_MODEL = "openrouter/google/gemini-2.0-flash-001"
36
36
  # Temperature for reproducible scoring
37
37
  SCORING_TEMPERATURE = 0.1
38
38
 
39
+ # Maximum token limit to prevent cost explosion
40
+ MAX_TOKEN_LIMIT = 8192
41
+
39
42
 
40
43
  class ComponentScore(BaseModel):
41
44
  """Individual component score with justification.
@@ -106,6 +109,17 @@ class AmbiguityScorer:
106
109
  Uses LLM to evaluate clarity of goals, constraints, and success criteria
107
110
  from interview conversation, producing reproducible scores.
108
111
 
112
+ Uses adaptive token allocation: starts with `initial_max_tokens` and
113
+ doubles on truncation up to `MAX_TOKEN_LIMIT`. Retries up to `max_retries`
114
+ times on both provider errors and parse failures.
115
+
116
+ Attributes:
117
+ llm_adapter: The LLM adapter for completions.
118
+ model: Model identifier to use.
119
+ temperature: Temperature for reproducibility (default 0.1).
120
+ initial_max_tokens: Starting token limit (default 2048).
121
+ max_retries: Maximum retry attempts (default 3).
122
+
109
123
  Example:
110
124
  scorer = AmbiguityScorer(llm_adapter=LiteLLMAdapter())
111
125
 
@@ -123,7 +137,8 @@ class AmbiguityScorer:
123
137
  llm_adapter: LiteLLMAdapter
124
138
  model: str = DEFAULT_MODEL
125
139
  temperature: float = SCORING_TEMPERATURE
126
- max_tokens: int = 2048
140
+ initial_max_tokens: int = 2048
141
+ max_retries: int = 3
127
142
 
128
143
  async def score(
129
144
  self, state: InterviewState
@@ -135,6 +150,9 @@ class AmbiguityScorer:
135
150
  - Constraints (30% weight)
136
151
  - Success criteria (30% weight)
137
152
 
153
+ Uses adaptive token allocation: starts with initial_max_tokens and
154
+ doubles on parse failure, up to max_retries attempts.
155
+
138
156
  Args:
139
157
  state: The interview state to score.
140
158
 
@@ -159,57 +177,98 @@ class AmbiguityScorer:
159
177
  Message(role=MessageRole.USER, content=user_prompt),
160
178
  ]
161
179
 
162
- config = CompletionConfig(
163
- model=self.model,
164
- temperature=self.temperature,
165
- max_tokens=self.max_tokens,
166
- )
167
-
168
- result = await self.llm_adapter.complete(messages, config)
180
+ current_max_tokens = self.initial_max_tokens
181
+ last_error: Exception | ProviderError | None = None
182
+ last_response: str = ""
169
183
 
170
- if result.is_err:
171
- log.warning(
172
- "ambiguity.scoring.failed",
173
- interview_id=state.interview_id,
174
- error=str(result.error),
184
+ for attempt in range(self.max_retries):
185
+ config = CompletionConfig(
186
+ model=self.model,
187
+ temperature=self.temperature,
188
+ max_tokens=current_max_tokens,
175
189
  )
176
- return Result.err(result.error)
177
-
178
- # Parse the LLM response into scores
179
- try:
180
- breakdown = self._parse_scoring_response(result.value.content)
181
- overall_score = self._calculate_overall_score(breakdown)
182
190
 
183
- ambiguity_score = AmbiguityScore(
184
- overall_score=overall_score,
185
- breakdown=breakdown,
186
- )
191
+ result = await self.llm_adapter.complete(messages, config)
192
+
193
+ # Fix #3: Retry on provider errors (rate limits, transient failures)
194
+ if result.is_err:
195
+ last_error = result.error
196
+ log.warning(
197
+ "ambiguity.scoring.provider_error_retrying",
198
+ interview_id=state.interview_id,
199
+ error=str(result.error),
200
+ attempt=attempt + 1,
201
+ max_retries=self.max_retries,
202
+ )
203
+ continue
187
204
 
188
- log.info(
189
- "ambiguity.scoring.completed",
190
- interview_id=state.interview_id,
191
- overall_score=overall_score,
192
- is_ready_for_seed=ambiguity_score.is_ready_for_seed,
193
- goal_clarity=breakdown.goal_clarity.clarity_score,
194
- constraint_clarity=breakdown.constraint_clarity.clarity_score,
195
- success_criteria_clarity=breakdown.success_criteria_clarity.clarity_score,
196
- )
205
+ # Parse the LLM response into scores
206
+ try:
207
+ breakdown = self._parse_scoring_response(result.value.content)
208
+ overall_score = self._calculate_overall_score(breakdown)
197
209
 
198
- return Result.ok(ambiguity_score)
210
+ ambiguity_score = AmbiguityScore(
211
+ overall_score=overall_score,
212
+ breakdown=breakdown,
213
+ )
199
214
 
200
- except (ValueError, KeyError) as e:
201
- log.warning(
202
- "ambiguity.scoring.parse_failed",
203
- interview_id=state.interview_id,
204
- error=str(e),
205
- response=result.value.content[:500],
206
- )
207
- return Result.err(
208
- ProviderError(
209
- f"Failed to parse scoring response: {e}",
210
- details={"response_preview": result.value.content[:200]},
215
+ log.info(
216
+ "ambiguity.scoring.completed",
217
+ interview_id=state.interview_id,
218
+ overall_score=overall_score,
219
+ is_ready_for_seed=ambiguity_score.is_ready_for_seed,
220
+ goal_clarity=breakdown.goal_clarity.clarity_score,
221
+ constraint_clarity=breakdown.constraint_clarity.clarity_score,
222
+ success_criteria_clarity=breakdown.success_criteria_clarity.clarity_score,
223
+ tokens_used=current_max_tokens,
224
+ attempt=attempt + 1,
211
225
  )
226
+
227
+ return Result.ok(ambiguity_score)
228
+
229
+ except (ValueError, KeyError) as e:
230
+ last_error = e
231
+ last_response = result.value.content
232
+
233
+ # Fix #2: Only increase tokens if response was truncated
234
+ is_truncated = result.value.finish_reason == "length"
235
+
236
+ if is_truncated:
237
+ # Fix #1: Cap token growth with MAX_TOKEN_LIMIT
238
+ next_tokens = min(current_max_tokens * 2, MAX_TOKEN_LIMIT)
239
+ log.warning(
240
+ "ambiguity.scoring.truncated_retrying",
241
+ interview_id=state.interview_id,
242
+ error=str(e),
243
+ attempt=attempt + 1,
244
+ current_tokens=current_max_tokens,
245
+ next_tokens=next_tokens,
246
+ )
247
+ current_max_tokens = next_tokens
248
+ else:
249
+ # Format error without truncation - retry with same tokens
250
+ log.warning(
251
+ "ambiguity.scoring.format_error_retrying",
252
+ interview_id=state.interview_id,
253
+ error=str(e),
254
+ attempt=attempt + 1,
255
+ finish_reason=result.value.finish_reason,
256
+ )
257
+
258
+ # All retries exhausted
259
+ log.warning(
260
+ "ambiguity.scoring.failed",
261
+ interview_id=state.interview_id,
262
+ error=str(last_error),
263
+ response=last_response[:500] if last_response else None,
264
+ max_retries_exhausted=True,
265
+ )
266
+ return Result.err(
267
+ ProviderError(
268
+ f"Failed to parse scoring response after {self.max_retries} attempts: {last_error}",
269
+ details={"response_preview": last_response[:200] if last_response else None},
212
270
  )
271
+ )
213
272
 
214
273
  def _build_interview_context(self, state: InterviewState) -> str:
215
274
  """Build context string from interview state.
@@ -254,15 +313,17 @@ Evaluate three components:
254
313
 
255
314
  For each component, provide:
256
315
  - A clarity score between 0.0 (completely unclear) and 1.0 (perfectly clear)
257
- - A brief justification explaining the score
316
+ - A brief justification (1-2 sentences max) explaining the score
317
+
318
+ IMPORTANT: You MUST provide ALL six fields below. Keep justifications concise.
258
319
 
259
320
  Respond in this exact format:
260
321
  GOAL_CLARITY_SCORE: <score>
261
- GOAL_CLARITY_JUSTIFICATION: <justification>
322
+ GOAL_CLARITY_JUSTIFICATION: <justification in 1-2 sentences>
262
323
  CONSTRAINT_CLARITY_SCORE: <score>
263
- CONSTRAINT_CLARITY_JUSTIFICATION: <justification>
324
+ CONSTRAINT_CLARITY_JUSTIFICATION: <justification in 1-2 sentences>
264
325
  SUCCESS_CRITERIA_CLARITY_SCORE: <score>
265
- SUCCESS_CRITERIA_CLARITY_JUSTIFICATION: <justification>
326
+ SUCCESS_CRITERIA_CLARITY_JUSTIFICATION: <justification in 1-2 sentences>
266
327
 
267
328
  Be strict in your evaluation. Scores above 0.8 require very specific, measurable requirements."""
268
329
 
@@ -17,6 +17,7 @@ from pydantic import BaseModel, Field
17
17
  import structlog
18
18
 
19
19
  from ouroboros.core.errors import ProviderError, ValidationError
20
+ from ouroboros.core.security import InputValidator
20
21
  from ouroboros.core.types import Result
21
22
  from ouroboros.providers.base import (
22
23
  CompletionConfig,
@@ -186,9 +187,11 @@ class InterviewEngine:
186
187
  Returns:
187
188
  Result containing the new InterviewState or ValidationError.
188
189
  """
189
- if not initial_context.strip():
190
+ # Validate initial context with security limits
191
+ is_valid, error_msg = InputValidator.validate_initial_context(initial_context)
192
+ if not is_valid:
190
193
  return Result.err(
191
- ValidationError("Initial context cannot be empty", field="initial_context")
194
+ ValidationError(error_msg, field="initial_context")
192
195
  )
193
196
 
194
197
  if interview_id is None:
@@ -285,9 +288,11 @@ class InterviewEngine:
285
288
  Returns:
286
289
  Result containing updated state or ValidationError.
287
290
  """
288
- if not user_response.strip():
291
+ # Validate user response with security limits
292
+ is_valid, error_msg = InputValidator.validate_user_response(user_response)
293
+ if not is_valid:
289
294
  return Result.err(
290
- ValidationError("User response cannot be empty", field="user_response")
295
+ ValidationError(error_msg, field="user_response")
291
296
  )
292
297
 
293
298
  if state.is_complete:
@@ -15,6 +15,7 @@ import yaml
15
15
 
16
16
  from ouroboros.cli.formatters import console
17
17
  from ouroboros.cli.formatters.panels import print_error, print_info, print_success
18
+ from ouroboros.core.security import InputValidator
18
19
 
19
20
  app = typer.Typer(
20
21
  name="run",
@@ -33,8 +34,15 @@ def _load_seed_from_yaml(seed_file: Path) -> dict:
33
34
  Seed configuration dictionary.
34
35
 
35
36
  Raises:
36
- typer.Exit: If file cannot be loaded.
37
+ typer.Exit: If file cannot be loaded or exceeds size limit.
37
38
  """
39
+ # Security: Validate file size to prevent DoS
40
+ file_size = seed_file.stat().st_size
41
+ is_valid, error_msg = InputValidator.validate_seed_file_size(file_size)
42
+ if not is_valid:
43
+ print_error(f"Seed file validation failed: {error_msg}")
44
+ raise typer.Exit(1)
45
+
38
46
  try:
39
47
  with open(seed_file) as f:
40
48
  return yaml.safe_load(f)
@@ -27,6 +27,12 @@ from ouroboros.core.seed import (
27
27
  Seed,
28
28
  SeedMetadata,
29
29
  )
30
+ from ouroboros.core.security import (
31
+ InputValidator,
32
+ mask_api_key,
33
+ sanitize_for_logging,
34
+ validate_api_key_format,
35
+ )
30
36
  from ouroboros.core.types import CostUnits, DriftScore, EventPayload, Result
31
37
 
32
38
  __all__ = [
@@ -59,4 +65,9 @@ __all__ = [
59
65
  "compress_context",
60
66
  "compress_context_with_llm",
61
67
  "create_filtered_context",
68
+ # Security utilities
69
+ "InputValidator",
70
+ "mask_api_key",
71
+ "validate_api_key_format",
72
+ "sanitize_for_logging",
62
73
  ]