dialectical-framework 1.5.4__tar.gz → 1.5.6__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 (187) hide show
  1. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/PKG-INFO +1 -1
  2. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/pyproject.toml +1 -1
  3. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/agents/analyst/skills/expand_polarities.py +61 -39
  4. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/agents/analyst/system_prompts.py +8 -1
  5. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/agents/apps.py +5 -2
  6. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/concerns/create_nexus.py +10 -2
  7. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/concerns/perspective_combination.py +54 -41
  8. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/graph/nodes/cycle.py +24 -9
  9. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/LICENSE +0 -0
  10. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/README.md +0 -0
  11. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/__init__.py +0 -0
  12. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/agents/__init__.py +0 -0
  13. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/agents/advisor/__init__.py +0 -0
  14. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/agents/advisor/advisor.py +0 -0
  15. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/agents/advisor/system_prompts.py +0 -0
  16. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/agents/advisor/tools/__init__.py +0 -0
  17. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/agents/advisor/tools/anchor.py +0 -0
  18. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/agents/advisor/tools/explore.py +0 -0
  19. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/agents/advisor/tools/ingest.py +0 -0
  20. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/agents/advisor/tools/sync.py +0 -0
  21. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/agents/agent_context.py +0 -0
  22. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/agents/analyst/__init__.py +0 -0
  23. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/agents/analyst/analyst.py +0 -0
  24. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/agents/analyst/skills/__init__.py +0 -0
  25. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/agents/analyst/skills/anchor_theses.py +0 -0
  26. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/agents/analyst/skills/edit_perspective.py +0 -0
  27. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/agents/analyst/skills/find_polarities.py +0 -0
  28. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/agents/analyst/skills/introduce_polarity.py +0 -0
  29. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/agents/analyst/skills/surface_theses.py +0 -0
  30. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/agents/analyst/tools/__init__.py +0 -0
  31. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/agents/analyst/tools/create_dx_input.py +0 -0
  32. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/agents/analyst/tools/place_statement.py +0 -0
  33. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/agents/conversation_facilitator.py +0 -0
  34. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/agents/execution_report.py +0 -0
  35. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/agents/explorer/__init__.py +0 -0
  36. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/agents/explorer/explorer.py +0 -0
  37. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/agents/explorer/skills/__init__.py +0 -0
  38. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/agents/explorer/skills/build_wheels.py +0 -0
  39. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/agents/explorer/skills/explore_transformations.py +0 -0
  40. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/agents/explorer/skills/generate_synthesis.py +0 -0
  41. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/agents/explorer/system_prompts.py +0 -0
  42. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/agents/explorer/tools/__init__.py +0 -0
  43. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/agents/explorer/tools/create_nexus.py +0 -0
  44. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/agents/explorer/tools/expand_nexus.py +0 -0
  45. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/agents/explorer/tools/generate_synthesis.py +0 -0
  46. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/agents/explorer/tools/present_exploration.py +0 -0
  47. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/agents/orchestrator/__init__.py +0 -0
  48. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/agents/orchestrator/tools/__init__.py +0 -0
  49. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/agents/orchestrator/tools/add_input.py +0 -0
  50. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/agents/orchestrator/tools/digest_input.py +0 -0
  51. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/agents/orchestrator/tools/discard.py +0 -0
  52. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/agents/orchestrator/tools/get_schema.py +0 -0
  53. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/agents/orchestrator/tools/inspect_node.py +0 -0
  54. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/agents/orchestrator/tools/present_analysis.py +0 -0
  55. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/agents/orchestrator/tools/query_graph.py +0 -0
  56. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/agents/orchestrator/tools/read_digest.py +0 -0
  57. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/agents/orchestrator/tools/read_input.py +0 -0
  58. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/agents/reasonable_concern.py +0 -0
  59. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/agents/stream_events.py +0 -0
  60. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/concerns/__init__.py +0 -0
  61. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/concerns/ac_re_taxonomy.py +0 -0
  62. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/concerns/action_extraction.py +0 -0
  63. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/concerns/add_input.py +0 -0
  64. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/concerns/ai_dto/__init__.py +0 -0
  65. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/concerns/ai_dto/statement_dto.py +0 -0
  66. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/concerns/ai_dto/statements_deck_dto.py +0 -0
  67. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/concerns/antithesis_classification.py +0 -0
  68. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/concerns/antithesis_extraction.py +0 -0
  69. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/concerns/aspect_classification.py +0 -0
  70. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/concerns/aspect_generation.py +0 -0
  71. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/concerns/causality/__init__.py +0 -0
  72. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/concerns/causality/causality_estimator.py +0 -0
  73. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/concerns/causality/causality_estimator_balanced.py +0 -0
  74. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/concerns/causality/causality_estimator_criteria.py +0 -0
  75. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/concerns/causality/causality_estimator_desirable.py +0 -0
  76. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/concerns/causality/causality_estimator_feasible.py +0 -0
  77. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/concerns/causality/causality_estimator_realistic.py +0 -0
  78. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/concerns/causality/causality_normalizer.py +0 -0
  79. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/concerns/causality/estimator_resolver.py +0 -0
  80. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/concerns/causality_estimation.py +0 -0
  81. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/concerns/control_statements_check.py +0 -0
  82. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/concerns/create_dx_input.py +0 -0
  83. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/concerns/diagonal_oppositions_check.py +0 -0
  84. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/concerns/dialectical_context.py +0 -0
  85. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/concerns/discard.py +0 -0
  86. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/concerns/display_text_edit.py +0 -0
  87. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/concerns/expand_nexus.py +0 -0
  88. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/concerns/perspective_validation.py +0 -0
  89. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/concerns/positive_ac_re_apex_derivation.py +0 -0
  90. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/concerns/source_digest.py +0 -0
  91. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/concerns/statement_classification.py +0 -0
  92. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/concerns/statement_deduplication.py +0 -0
  93. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/concerns/statement_placement.py +0 -0
  94. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/concerns/synthesis_generation.py +0 -0
  95. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/concerns/thesis_extraction.py +0 -0
  96. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/concerns/transformation_audit.py +0 -0
  97. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/concerns/transformation_generation.py +0 -0
  98. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/dialectical_reasoning.py +0 -0
  99. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/enums/__init__.py +0 -0
  100. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/enums/causality_preset.py +0 -0
  101. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/enums/di.py +0 -0
  102. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/events/__init__.py +0 -0
  103. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/events/graph_event.py +0 -0
  104. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/events/graph_event_bus.py +0 -0
  105. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/exceptions/__init__.py +0 -0
  106. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/exceptions/node_errors.py +0 -0
  107. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/exceptions/resolver_errors.py +0 -0
  108. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/graph/__init__.py +0 -0
  109. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/graph/composite_input_resolver.py +0 -0
  110. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/graph/dialexity_input_resolver.py +0 -0
  111. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/graph/estimation_manager.py +0 -0
  112. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/graph/mixins/__init__.py +0 -0
  113. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/graph/mixins/incremental_build_mixin.py +0 -0
  114. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/graph/mixins/intent_mixin.py +0 -0
  115. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/graph/mixins/persistable_mixin.py +0 -0
  116. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/graph/nodes/__init__.py +0 -0
  117. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/graph/nodes/assessable_entity.py +0 -0
  118. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/graph/nodes/base_node.py +0 -0
  119. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/graph/nodes/case.py +0 -0
  120. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/graph/nodes/estimation.py +0 -0
  121. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/graph/nodes/ideas.py +0 -0
  122. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/graph/nodes/input.py +0 -0
  123. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/graph/nodes/nexus.py +0 -0
  124. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/graph/nodes/perspective.py +0 -0
  125. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/graph/nodes/polarity.py +0 -0
  126. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/graph/nodes/rationale.py +0 -0
  127. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/graph/nodes/statement.py +0 -0
  128. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/graph/nodes/synthesis.py +0 -0
  129. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/graph/nodes/transformation.py +0 -0
  130. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/graph/nodes/transition.py +0 -0
  131. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/graph/nodes/wheel.py +0 -0
  132. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/graph/relationship_manager.py +0 -0
  133. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/graph/relationships/__init__.py +0 -0
  134. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/graph/relationships/action_reflection_relationship.py +0 -0
  135. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/graph/relationships/belongs_to_cycle_relationship.py +0 -0
  136. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/graph/relationships/belongs_to_nexus_relationship.py +0 -0
  137. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/graph/relationships/changed_to_relationship.py +0 -0
  138. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/graph/relationships/contradiction_of_relationship.py +0 -0
  139. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/graph/relationships/critiques_relationship.py +0 -0
  140. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/graph/relationships/distilled_to_relationship.py +0 -0
  141. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/graph/relationships/estimates_relationship.py +0 -0
  142. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/graph/relationships/explains_relationship.py +0 -0
  143. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/graph/relationships/has_input_relationship.py +0 -0
  144. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/graph/relationships/has_statement_relationship.py +0 -0
  145. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/graph/relationships/has_wheel_relationship.py +0 -0
  146. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/graph/relationships/immutable_structure.py +0 -0
  147. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/graph/relationships/is_source_of_relationship.py +0 -0
  148. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/graph/relationships/is_target_of_relationship.py +0 -0
  149. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/graph/relationships/negative_side_of_relationship.py +0 -0
  150. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/graph/relationships/opposite_direction_relationship.py +0 -0
  151. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/graph/relationships/opposite_of_relationship.py +0 -0
  152. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/graph/relationships/polarity_relationship.py +0 -0
  153. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/graph/relationships/positive_side_of_relationship.py +0 -0
  154. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/graph/relationships/provides_relationship.py +0 -0
  155. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/graph/relationships/synthesis_of_relationship.py +0 -0
  156. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/graph/rendering.py +0 -0
  157. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/graph/repositories/__init__.py +0 -0
  158. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/graph/repositories/case_repository.py +0 -0
  159. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/graph/repositories/cycle_repository.py +0 -0
  160. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/graph/repositories/input_repository.py +0 -0
  161. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/graph/repositories/nexus_repository.py +0 -0
  162. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/graph/repositories/node_repository.py +0 -0
  163. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/graph/repositories/perspective_repository.py +0 -0
  164. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/graph/repositories/polarity_repository.py +0 -0
  165. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/graph/repositories/schema_repository.py +0 -0
  166. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/graph/repositories/statement_repository.py +0 -0
  167. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/graph/repositories/transformation_repository.py +0 -0
  168. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/graph/repositories/wheel_repository.py +0 -0
  169. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/graph/scope_context.py +0 -0
  170. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/graph/verbatim_input_resolver.py +0 -0
  171. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/graph/wheel_segment.py +0 -0
  172. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/graph/wheel_segment_polar_pair.py +0 -0
  173. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/protocols/__init__.py +0 -0
  174. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/protocols/has_config.py +0 -0
  175. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/protocols/input_resolver.py +0 -0
  176. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/settings.py +0 -0
  177. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/utils/__init__.py +0 -0
  178. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/utils/bedrock_provider.py +0 -0
  179. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/utils/concurrency.py +0 -0
  180. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/utils/dc_replace.py +0 -0
  181. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/utils/decompose_probability_uniformly.py +0 -0
  182. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/utils/edge_context.py +0 -0
  183. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/utils/effect_logger.py +0 -0
  184. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/utils/input_context.py +0 -0
  185. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/utils/order_transitions.py +0 -0
  186. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/utils/sequence_generation.py +0 -0
  187. {dialectical_framework-1.5.4 → dialectical_framework-1.5.6}/src/dialectical_framework/utils/use_brain.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: dialectical-framework
3
- Version: 1.5.4
3
+ Version: 1.5.6
4
4
  Summary: A dialectical framework for augmented intelligence. AI reasoning powered with dialectics supports humans in: system optimization (psychology, engineering, business, politics, etc.); dispute resolution (mediation, conflicts, negotiations, etc.); decision-making (dilemmas, challenging situations, win-win, etc.).
5
5
  License: MIT
6
6
  Keywords: dialectics,dialectical-reasoning,synthesis,thesis-antithesis,ai,artificial-intelligence,llm,reasoning-framework,philosophy,logic,argumentation,conflict-resolution,decision-making,critical-thinking,semantic-graph,mirascope,pydantic,perspectives,polarity-reasoning
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "dialectical-framework"
3
- version = "1.5.4"
3
+ version = "1.5.6"
4
4
  description = "A dialectical framework for augmented intelligence. AI reasoning powered with dialectics supports humans in: system optimization (psychology, engineering, business, politics, etc.); dispute resolution (mediation, conflicts, negotiations, etc.); decision-making (dilemmas, challenging situations, win-win, etc.)."
5
5
  authors = ["Evaldas Taroza <evaldas@dialexity.com>"]
6
6
  readme = "README.md"
@@ -18,27 +18,36 @@ from pydantic import Field
18
18
 
19
19
  from dialectical_framework.agents.reasonable_concern import ReasonableConcern
20
20
  from dialectical_framework.enums.di import DI
21
- from dialectical_framework.concerns.aspect_generation import (AspectGeneration,
22
- AspectResult)
23
- from dialectical_framework.concerns.statement_deduplication import \
24
- StatementDeduplication
21
+ from dialectical_framework.concerns.aspect_generation import (
22
+ AspectGeneration,
23
+ AspectResult,
24
+ )
25
+ from dialectical_framework.concerns.statement_deduplication import (
26
+ StatementDeduplication,
27
+ )
25
28
  from dialectical_framework.graph.nodes.polarity import Polarity
26
- from dialectical_framework.graph.nodes.perspective import (POSITION_A_MINUS,
27
- POSITION_A_PLUS,
28
- POSITION_T_MINUS,
29
- POSITION_T_PLUS,
30
- Perspective)
29
+ from dialectical_framework.graph.nodes.perspective import (
30
+ POSITION_A_MINUS,
31
+ POSITION_A_PLUS,
32
+ POSITION_T_MINUS,
33
+ POSITION_T_PLUS,
34
+ Perspective,
35
+ )
31
36
  from dialectical_framework.graph.relationships.polarity_relationship import (
32
- AMinusRelationship, APlusRelationship, HasPolarityRelationship,
33
- TMinusRelationship, TPlusRelationship)
34
- from dialectical_framework.graph.repositories.statement_repository import \
35
- StatementRepository
36
- from dialectical_framework.graph.repositories.input_repository import \
37
- InputRepository
38
- from dialectical_framework.graph.repositories.node_repository import \
39
- NodeRepository
40
- from dialectical_framework.graph.repositories.perspective_repository import \
41
- PerspectiveRepository
37
+ AMinusRelationship,
38
+ APlusRelationship,
39
+ HasPolarityRelationship,
40
+ TMinusRelationship,
41
+ TPlusRelationship,
42
+ )
43
+ from dialectical_framework.graph.repositories.statement_repository import (
44
+ StatementRepository,
45
+ )
46
+ from dialectical_framework.graph.repositories.input_repository import InputRepository
47
+ from dialectical_framework.graph.repositories.node_repository import NodeRepository
48
+ from dialectical_framework.graph.repositories.perspective_repository import (
49
+ PerspectiveRepository,
50
+ )
42
51
 
43
52
  if TYPE_CHECKING:
44
53
  from dialectical_framework.protocols.input_resolver import InputResolver
@@ -51,20 +60,22 @@ class ExpandPolarity(ReasonableConcern[list[Perspective]]):
51
60
  Creates Perspectives from a Polarity by generating and connecting aspects
52
61
  (T+, T-, A+, A-).
53
62
 
54
- Each call produces one NEW perspective (alternative tetrad) for the Polarity,
55
- using `not_like_these` to ensure diversity from existing ones. Call multiple
56
- times on the same Polarity to build a richer space of tetrads.
63
+ Each call produces `count` NEW perspectives (alternative tetrads) for the
64
+ Polarity, generated sequentially so each uses `not_like_these` (existing +
65
+ already-generated-this-call) to ensure diversity. Any pre-existing partial
66
+ Perspective counts toward `count`.
57
67
 
58
68
  Flow:
59
69
  1. Resolve Polarity by hash
60
70
  2. Look up existing Perspectives for this Polarity
61
- 3. If none exist, create a new Perspective
62
- 4. Complete all partial PPs by generating aspects
71
+ 3. Create new partial Perspectives so `count` are generated this call
72
+ 4. Complete all partial PPs sequentially by generating aspects
63
73
  5. Return list of completed Perspectives (existing + new)
64
74
  """
65
75
 
66
- def __init__(self, polarity_hash: str) -> None:
76
+ def __init__(self, polarity_hash: str, count: int = 1) -> None:
67
77
  self.polarity_hash = polarity_hash
78
+ self.count = max(1, count)
68
79
 
69
80
  async def resolve(self) -> list[Perspective]:
70
81
  """
@@ -86,20 +97,17 @@ class ExpandPolarity(ReasonableConcern[list[Perspective]]):
86
97
  pp_repo = PerspectiveRepository()
87
98
  existing_pps = pp_repo.find_by_polarity(polarity)
88
99
 
89
- if not existing_pps:
90
- # No PP exists - create one referencing the Polarity
91
- pp = self._create_perspective_for_polarity(polarity)
92
- existing_pps = [pp]
93
-
94
100
  complete_pps = [pp for pp in existing_pps if pp.is_complete()]
95
101
  partial_pps = [pp for pp in existing_pps if not pp.is_complete()]
96
102
 
97
- if not partial_pps:
98
- # All existing perspectives are complete create a new one
99
- pp = self._create_perspective_for_polarity(polarity)
100
- partial_pps = [pp]
103
+ # Complete any existing partials, then create additional new ones so that
104
+ # `count` fresh Perspectives are produced this call (a pre-existing partial
105
+ # counts as one of them).
106
+ additional_needed = self.count - len(partial_pps)
107
+ for _ in range(max(0, additional_needed)):
108
+ partial_pps.append(self._create_perspective_for_polarity(polarity))
101
109
 
102
- # Complete all partial PPs
110
+ # Complete all partial PPs sequentially (each sees prior results)
103
111
  completed_pps: list[Perspective] = []
104
112
 
105
113
  for pp in partial_pps:
@@ -209,7 +217,12 @@ class ExpandPolarity(ReasonableConcern[list[Perspective]]):
209
217
 
210
218
  def _perspective_final_state(self, pp: Perspective) -> dict[str, str | None]:
211
219
  """Build a dict with the final post-dedup text at each position."""
212
- positions = [POSITION_T_PLUS, POSITION_T_MINUS, POSITION_A_PLUS, POSITION_A_MINUS]
220
+ positions = [
221
+ POSITION_T_PLUS,
222
+ POSITION_T_MINUS,
223
+ POSITION_A_PLUS,
224
+ POSITION_A_MINUS,
225
+ ]
213
226
  state: dict[str, str | None] = {"hash": pp.short_hash}
214
227
  for pos in positions:
215
228
  manager = pp.get_relationship_manager_by_position(pos)
@@ -299,13 +312,22 @@ class ExpandPolarity(ReasonableConcern[list[Perspective]]):
299
312
 
300
313
  @llm.tool
301
314
  async def expand_polarities(
302
- polarity_hashes: Annotated[list[str], Field(description="Hashes of Polarities to expand into full Perspectives")],
315
+ polarity_hashes: Annotated[
316
+ list[str],
317
+ Field(description="Hashes of Polarities to expand into full Perspectives"),
318
+ ],
319
+ count: Annotated[
320
+ int,
321
+ Field(
322
+ description="Number of new Perspectives to generate per Polarity (each is diverse from prior ones)"
323
+ ),
324
+ ] = 1,
303
325
  ) -> str:
304
- """Build complete Perspectives from Polarities by generating evaluative aspects (T+, T-, A+, A-) for each. Each call generates one new Perspective per Polarity — call multiple times for alternative perspectives on the same Polarity (duplicates in a single call are ignored). The Polarities must already exist in the graph."""
326
+ """Build complete Perspectives from Polarities by generating evaluative aspects (T+, T-, A+, A-) for each. Generates `count` new Perspectives per Polarity sequentially each sees prior tetrads to ensure diversity. The Polarities must already exist in the graph."""
305
327
  import asyncio
306
328
 
307
329
  async def _expand_one(h: str) -> str:
308
- concern = ExpandPolarity(polarity_hash=h)
330
+ concern = ExpandPolarity(polarity_hash=h, count=count)
309
331
  await concern.resolve()
310
332
  return str(concern.report)
311
333
 
@@ -35,6 +35,13 @@ Choose your approach based on the user's input:
35
35
  - User is working step-by-step and asks for the next step
36
36
  - User refers to specific existing theses and wants tensions found
37
37
  - User explicitly asks to expand a polarity into perspectives
38
+ - User asks to "find tetrades", "show aspects", "build T+/T-/A+/A-", or otherwise requests the four-aspect structure
39
+
40
+ **Disambiguating "perspectives", "angles", "points of view":**
41
+ These words map to DIFFERENT tools depending on what's in scope:
42
+ - Polarity in view/referenced → `expand_polarities` (build the tetrad for that polarity)
43
+ - Thesis in view/referenced → `find_polarities` (find opposing angles)
44
+ - No specific node → check what exists: unexpanded polarities → `expand_polarities`; theses without polarities → `find_polarities`
38
45
 
39
46
  **Use `add_input` when:**
40
47
  - User provides multi-sentence source material (a paragraph, article, or detailed description) to capture before analysis
@@ -93,7 +100,7 @@ When the user wants to explore interactions between perspectives:
93
100
  - `surface_theses` — Extract theses from inputs (requires inputs in scope).
94
101
  - `find_polarities` — Find antitheses for existing theses.
95
102
  - `introduce_polarity` — Directly introduce a known T-A tension (both sides clear).
96
- - `expand_polarities` — Expand polarities into full Perspectives (T+/T-/A+/A-).
103
+ - `expand_polarities` — Expand polarities into full Perspectives (T+/T-/A+/A-). Pass `count > 1` to generate several diverse tetrads per polarity in one call.
97
104
  - `place_statement` — Check if a statement exists in the graph already.
98
105
 
99
106
  **Steering:**
@@ -341,8 +341,11 @@ both sides contribute", "constructive co-existence", "where 1+1 > 2".
341
341
 
342
342
  ## Viewport Scope
343
343
 
344
- Messages may include context about what the user is currently viewing. This tells you their active focus. Respect it:
344
+ Messages may begin with `[User is currently viewing: ...]`. This tells you their active focus use it to resolve ambiguous requests:
345
345
 
346
+ - When the user asks for an action without specifying a target (e.g., "find tetrades", "expand", "show aspects", "find perspectives"), apply it to the node in their current view.
347
+ - If a polarity is in view and the user asks for tetrades/aspects/perspectives/angles, expand that polarity into its full structure.
348
+ - If a thesis is in view and the user asks for perspectives/angles/oppositions, find opposing viewpoints for that thesis.
346
349
  - Only operate on the node(s) in the user's viewport unless they explicitly reference something else or ask to switch.
347
350
  - Never silently pivot to other nodes outside the viewport. If you think working on a different node would help, ask first.
348
351
 
@@ -487,5 +490,5 @@ rules. Adjust interaction accordingly:
487
490
  - Present tetrads structurally with all six positions and scores.
488
491
  - Show transformation positions explicitly: Ac, Ac+, Ac-, Re, Re+, Re-.
489
492
  - Show control statements, modality alignment, diagonal contradictions.
490
- - Suggest next steps but don't auto-execute — the user drives.
493
+ - Suggest exploration-phase next steps (nexus, wheels) rather than auto-executing — the user drives structural expansion. Analysis tools (expand, find) still follow "act on clear intent."
491
494
  """
@@ -10,8 +10,7 @@ from typing import Optional
10
10
  from dialectical_framework.agents.reasonable_concern import ReasonableConcern
11
11
  from dialectical_framework.graph.nodes.nexus import Nexus
12
12
  from dialectical_framework.graph.nodes.perspective import Perspective
13
- from dialectical_framework.graph.repositories.node_repository import \
14
- NodeRepository
13
+ from dialectical_framework.graph.repositories.node_repository import NodeRepository
15
14
  from dialectical_framework.protocols.has_config import SettingsAware
16
15
 
17
16
 
@@ -48,7 +47,16 @@ class CreateNexus(SettingsAware, ReasonableConcern[CreateNexusResult]):
48
47
 
49
48
  repo = NodeRepository()
50
49
  perspectives: list[Perspective] = []
50
+ # Dedup input hashes, preserving order: BELONGS_TO_NEXUS is a directed
51
+ # relationship, so connect() does NOT deduplicate. A repeated hash here
52
+ # would create duplicate edges, causing nexus.perspectives.all() to
53
+ # yield the same PP twice and poisoning combination generation
54
+ # (degenerate cycles with repeated perspective_hashes).
55
+ seen_hashes: set[str] = set()
51
56
  for pp_hash in perspective_hashes:
57
+ if pp_hash in seen_hashes:
58
+ continue
59
+ seen_hashes.add(pp_hash)
52
60
  node = repo.find_by_hash(pp_hash, node_type=Perspective)
53
61
  if node is None:
54
62
  raise ValueError(f"Perspective not found: {pp_hash}")
@@ -43,7 +43,6 @@ from dialectical_framework.graph.repositories.wheel_repository import WheelRepos
43
43
  from dialectical_framework.protocols.has_config import SettingsAware
44
44
  from dialectical_framework.utils.sequence_generation import (
45
45
  generate_compatible_sequences,
46
- generate_permutation_sequences,
47
46
  )
48
47
 
49
48
  if TYPE_CHECKING:
@@ -123,26 +122,37 @@ class PerspectiveCombination(ReasonableConcern[CombinationResult], SettingsAware
123
122
  self._report.ok = False
124
123
  self._report.summary = "Nexus must be committed before combination"
125
124
  return CombinationResult(
126
- nexus=nexus, cycles=[], wheels=[],
127
- cycles_by_layer={}, wheels_by_layer={},
125
+ nexus=nexus,
126
+ cycles=[],
127
+ wheels=[],
128
+ cycles_by_layer={},
129
+ wheels_by_layer={},
128
130
  )
129
131
 
130
132
  if not perspectives:
131
133
  self._report.ok = False
132
134
  self._report.summary = "No Perspectives provided"
133
135
  return CombinationResult(
134
- nexus=nexus, cycles=[], wheels=[],
135
- cycles_by_layer={}, wheels_by_layer={},
136
+ nexus=nexus,
137
+ cycles=[],
138
+ wheels=[],
139
+ cycles_by_layer={},
140
+ wheels_by_layer={},
136
141
  )
137
142
 
138
143
  # Validate all PPs are committed
139
144
  for pp in perspectives:
140
145
  if not pp.is_committed:
141
146
  self._report.ok = False
142
- self._report.summary = "Perspective must be committed before combination"
147
+ self._report.summary = (
148
+ "Perspective must be committed before combination"
149
+ )
143
150
  return CombinationResult(
144
- nexus=nexus, cycles=[], wheels=[],
145
- cycles_by_layer={}, wheels_by_layer={},
151
+ nexus=nexus,
152
+ cycles=[],
153
+ wheels=[],
154
+ cycles_by_layer={},
155
+ wheels_by_layer={},
146
156
  )
147
157
 
148
158
  # Add PPs to Nexus (idempotent — skip already connected)
@@ -151,8 +161,19 @@ class PerspectiveCombination(ReasonableConcern[CombinationResult], SettingsAware
151
161
  self._report.artifacts["nexus_hash"] = nexus.short_hash
152
162
  self._report.artifacts["perspective_count"] = len(perspectives)
153
163
 
154
- # Build from all PPs in Nexus (not just the ones passed in)
155
- all_nexus_pps = [pp for pp, _ in nexus.perspectives.all()]
164
+ # Build from all PPs in Nexus (not just the ones passed in).
165
+ # Dedup by hash, preserving order: nexus.perspectives.all() can yield
166
+ # the same Perspective twice if a duplicate BELONGS_TO_NEXUS edge exists
167
+ # (BELONGS_TO_NEXUS is a directed relationship — connect() does NOT
168
+ # deduplicate it). combinations() over a list with repeats produces
169
+ # degenerate cycles whose perspective_hashes repeat a PP, corrupting
170
+ # all downstream wheel rendering.
171
+ seen: set[str] = set()
172
+ all_nexus_pps = [
173
+ pp
174
+ for pp, _ in nexus.perspectives.all()
175
+ if pp.hash not in seen and not seen.add(pp.hash)
176
+ ]
156
177
  total_pps = len(all_nexus_pps)
157
178
 
158
179
  # Build combinations layer by layer, collect only new structures
@@ -265,8 +286,8 @@ class PerspectiveCombination(ReasonableConcern[CombinationResult], SettingsAware
265
286
  """
266
287
  Generate T-cycle permutations for a PP combination.
267
288
 
268
- Uses generate_permutation_sequences to produce all (N-1)! unique
269
- T-cycle orderings (first element fixed to eliminate rotational duplicates).
289
+ Fixes the first perspective and permutes the rest to produce all
290
+ (N-1)! unique cycle orderings (circular rotation equivalence eliminated).
270
291
 
271
292
  Returns:
272
293
  Tuple of (all_cycles, new_cycles)
@@ -282,26 +303,17 @@ class PerspectiveCombination(ReasonableConcern[CombinationResult], SettingsAware
282
303
  new_cycles.append(cycle)
283
304
  return all_cycles, new_cycles
284
305
 
285
- # Extract T components and build component→PP lookup
286
- t_components: list[Statement] = []
287
- comp_hash_to_pp: dict[str, Perspective] = {}
306
+ # Validate all PPs have T components
288
307
  for pp in perspectives:
289
- t_result = pp.t.get()
290
- if not t_result:
291
- continue
292
- t_comp = t_result[0]
293
- t_components.append(t_comp)
294
- comp_hash_to_pp[t_comp.hash] = pp
308
+ if not pp.t.get():
309
+ return all_cycles, new_cycles
295
310
 
296
- if not t_components:
297
- return all_cycles, new_cycles
311
+ # Generate all (N-1)! orderings by fixing first PP, permuting rest
312
+ from itertools import permutations as itertools_permutations
298
313
 
299
- # Generate all (N-1)! permutation sequences
300
- sequences = generate_permutation_sequences(t_components)
301
-
302
- for sequence in sequences:
303
- # Map component sequence back to PP ordering
304
- ordered_pps = [comp_hash_to_pp[comp.hash] for comp in sequence]
314
+ first, rest = perspectives[0], perspectives[1:]
315
+ for perm in itertools_permutations(rest):
316
+ ordered_pps = [first, *perm]
305
317
 
306
318
  cycle, is_new = self._find_or_create_cycle(ordered_pps)
307
319
  all_cycles.append(cycle)
@@ -324,13 +336,17 @@ class PerspectiveCombination(ReasonableConcern[CombinationResult], SettingsAware
324
336
  Returns:
325
337
  Tuple of (Cycle, is_new)
326
338
  """
327
- from dialectical_framework.graph.repositories.cycle_repository import CycleRepository
339
+ from dialectical_framework.graph.repositories.cycle_repository import (
340
+ CycleRepository,
341
+ )
328
342
 
329
343
  # Layer-1 (single PP) Cycles have no causality intent (nothing to order)
330
344
  intent = self._preset if len(perspectives) >= 2 else None
331
345
 
332
346
  cycle_repo = CycleRepository()
333
- existing_cycles = cycle_repo.find_by_perspectives(perspectives, exact_order=True)
347
+ existing_cycles = cycle_repo.find_by_perspectives(
348
+ perspectives, exact_order=True
349
+ )
334
350
 
335
351
  for cycle in existing_cycles:
336
352
  if cycle.intent == intent:
@@ -402,9 +418,7 @@ class PerspectiveCombination(ReasonableConcern[CombinationResult], SettingsAware
402
418
  transition.commit()
403
419
  transition.cycle.connect(wheel)
404
420
  self._report.node_created(transition)
405
- self._report.relationship_created(
406
- transition.cycle, transition, wheel
407
- )
421
+ self._report.relationship_created(transition.cycle, transition, wheel)
408
422
 
409
423
  # Connect to cycle
410
424
  cycle.wheels.connect(wheel)
@@ -432,7 +446,9 @@ class PerspectiveCombination(ReasonableConcern[CombinationResult], SettingsAware
432
446
  Two sequences are opposite-direction if one is a circular reverse of the other.
433
447
  Connects pairs via the OPPOSITE_DIRECTION symmetric relationship.
434
448
  """
435
- from dialectical_framework.graph.repositories.cycle_repository import CycleRepository
449
+ from dialectical_framework.graph.repositories.cycle_repository import (
450
+ CycleRepository,
451
+ )
436
452
 
437
453
  cycle_repo = CycleRepository()
438
454
  wheel_repo = WheelRepository()
@@ -461,7 +477,7 @@ def _connect_opposite_direction_cycles(
461
477
 
462
478
  for i, cycle_a in enumerate(cycles):
463
479
  seq_a = cycle_a.perspective_hashes
464
- for cycle_b in cycles[i + 1:]:
480
+ for cycle_b in cycles[i + 1 :]:
465
481
  seq_b = cycle_b.perspective_hashes
466
482
  if _is_circular_reverse(seq_a, seq_b):
467
483
  pair_id = tuple(sorted([cycle_a.hash, cycle_b.hash]))
@@ -486,7 +502,7 @@ def _connect_opposite_direction_wheels(
486
502
  wheel_sequences.append((wheel, comp_hashes))
487
503
 
488
504
  for i, (wheel_a, seq_a) in enumerate(wheel_sequences):
489
- for wheel_b, seq_b in wheel_sequences[i + 1:]:
505
+ for wheel_b, seq_b in wheel_sequences[i + 1 :]:
490
506
  if _is_circular_reverse(seq_a, seq_b):
491
507
  pair_id = tuple(sorted([wheel_a.hash, wheel_b.hash]))
492
508
  if pair_id not in connected:
@@ -508,7 +524,4 @@ def _is_circular_reverse(seq_a: list[str], seq_b: list[str]) -> bool:
508
524
  if len(seq_a) <= 2:
509
525
  return False
510
526
  reversed_a = list(reversed(seq_a))
511
- return any(
512
- reversed_a[i:] + reversed_a[:i] == seq_b
513
- for i in range(len(reversed_a))
514
- )
527
+ return any(reversed_a[i:] + reversed_a[:i] == seq_b for i in range(len(reversed_a)))
@@ -14,7 +14,11 @@ from typing import ClassVar, Optional, TYPE_CHECKING
14
14
 
15
15
  from dialectical_framework.graph.nodes.assessable_entity import AssessableEntity
16
16
  from dialectical_framework.graph.mixins.intent_mixin import IntentMixin
17
- from dialectical_framework.graph.relationship_manager import RelationshipTo, RelationshipBoth, RelationshipManager
17
+ from dialectical_framework.graph.relationship_manager import (
18
+ RelationshipTo,
19
+ RelationshipBoth,
20
+ RelationshipManager,
21
+ )
18
22
  from dialectical_framework.graph.relationships.has_wheel_relationship import (
19
23
  HasWheelRelationship,
20
24
  )
@@ -82,7 +86,7 @@ class Cycle(IntentMixin, AssessableEntity, label="Cycle"):
82
86
  wheels: ClassVar[RelationshipManager[Wheel]] = RelationshipTo(
83
87
  "Wheel",
84
88
  model=HasWheelRelationship,
85
- cardinality=(0, None) # Zero or more wheels can implement this cycle
89
+ cardinality=(0, None), # Zero or more wheels can implement this cycle
86
90
  )
87
91
 
88
92
  # Opposite-direction counterpart (symmetric)
@@ -100,22 +104,31 @@ class Cycle(IntentMixin, AssessableEntity, label="Cycle"):
100
104
  Order determines the T-cycle: T1 → T2 → T3 → T1...
101
105
 
102
106
  Args:
103
- perspectives: Ordered list of committed Perspectives
107
+ perspectives: Ordered list of committed, distinct Perspectives
104
108
 
105
109
  Returns:
106
110
  Self for chaining
107
111
 
108
112
  Raises:
109
- ValueError: If any PP is not committed
113
+ ValueError: If any PP is not committed, or if any PP is repeated
110
114
  """
111
115
  hashes = []
112
116
  for pp in perspectives:
113
117
  if not pp.is_committed:
114
- raise ValueError(
115
- "Perspective must be committed before adding to Cycle"
116
- )
118
+ raise ValueError("Perspective must be committed before adding to Cycle")
117
119
  hashes.append(pp.hash)
118
120
 
121
+ # Reject duplicate perspectives. A cycle claiming to be "layer N"
122
+ # (N = len(perspective_hashes)) must reference N *distinct* PPs.
123
+ # Duplicates produce degenerate cycles (e.g. combinations() over a
124
+ # PP list poisoned by a transient duplicate BELONGS_TO_NEXUS edge)
125
+ # that render as malformed wheels. Fail loud so corruption is never
126
+ # persisted rather than surfacing later in the consuming app.
127
+ if len(set(hashes)) != len(hashes):
128
+ raise ValueError(
129
+ f"Cycle perspectives must be distinct; got duplicates in {hashes}"
130
+ )
131
+
119
132
  self.perspective_hashes = hashes
120
133
  self._pp_refs = perspectives # Keep refs for potential use
121
134
  return self
@@ -133,7 +146,10 @@ class Cycle(IntentMixin, AssessableEntity, label="Cycle"):
133
146
  if not self.perspective_hashes:
134
147
  return []
135
148
 
136
- from dialectical_framework.graph.repositories.node_repository import NodeRepository
149
+ from dialectical_framework.graph.repositories.node_repository import (
150
+ NodeRepository,
151
+ )
152
+
137
153
  repo = NodeRepository()
138
154
 
139
155
  result = []
@@ -268,4 +284,3 @@ class Cycle(IntentMixin, AssessableEntity, label="Cycle"):
268
284
  hash_str = self.short_hash if self.is_committed else "uncommitted"
269
285
  pp_count = len(self.perspective_hashes)
270
286
  return f"Cycle({hash_str}, pp_count={pp_count}, intent={self.intent})"
271
-