krons 0.2.2__tar.gz → 0.2.3__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.
- {krons-0.2.2 → krons-0.2.3}/.gitignore +2 -1
- krons-0.2.3/CLAUDE.md +183 -0
- {krons-0.2.2 → krons-0.2.3}/PKG-INFO +1 -1
- krons-0.2.3/examples/__init__.py +50 -0
- krons-0.2.3/examples/code_review_panel.py +452 -0
- krons-0.2.3/examples/codegen_pipeline.py +352 -0
- krons-0.2.3/examples/dynamic_response.py +171 -0
- krons-0.2.3/examples/event_sourcing.py +225 -0
- krons-0.2.3/examples/multi_agent_orchestration.py +429 -0
- krons-0.2.3/examples/pipeline_router.py +215 -0
- krons-0.2.3/examples/research_agent.py +425 -0
- krons-0.2.3/examples/tech_debate.py +496 -0
- krons-0.2.3/examples/validation_loop.py +304 -0
- {krons-0.2.2 → krons-0.2.3}/pyproject.toml +1 -1
- {krons-0.2.2 → krons-0.2.3}/src/krons/core/base/__init__.py +0 -4
- {krons-0.2.2 → krons-0.2.3}/src/krons/core/base/flow.py +7 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/session/session.py +54 -49
- krons-0.2.3/tests/session/test_session_logging.py +268 -0
- {krons-0.2.2 → krons-0.2.3}/uv.lock +1 -1
- krons-0.2.2/CLAUDE.md +0 -399
- krons-0.2.2/src/krons/core/base/log.py +0 -32
- krons-0.2.2/tests/core/test_log.py +0 -44
- krons-0.2.2/tests/session/test_session_logging.py +0 -337
- {krons-0.2.2 → krons-0.2.3}/.github/workflows/release.yml +0 -0
- {krons-0.2.2 → krons-0.2.3}/.python-version +0 -0
- {krons-0.2.2 → krons-0.2.3}/LICENSE +0 -0
- {krons-0.2.2 → krons-0.2.3}/README.md +0 -0
- {krons-0.2.2 → krons-0.2.3}/cookbooks/007_fan_out_in.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/cookbooks/test_conduct.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/__init__.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/agent/__init__.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/agent/mcps/__init__.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/agent/mcps/loader.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/agent/mcps/wrapper.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/agent/message/__init__.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/agent/message/action.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/agent/message/assistant.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/agent/message/common.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/agent/message/instruction.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/agent/message/prepare_msg.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/agent/message/role.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/agent/message/system.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/agent/operations/__init__.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/agent/operations/act.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/agent/operations/generate.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/agent/operations/llm_reparse.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/agent/operations/operate.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/agent/operations/parse.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/agent/operations/react.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/agent/operations/specs.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/agent/operations/structure.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/agent/operations/utils.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/agent/providers/__init__.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/agent/providers/anthropic_messages.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/agent/providers/claude_code.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/agent/providers/gemini.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/agent/providers/match.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/agent/providers/oai_chat.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/agent/third_party/__init__.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/agent/third_party/anthropic_models.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/agent/third_party/claude_code.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/agent/third_party/gemini_models.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/agent/third_party/openai_models.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/agent/tool.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/core/__init__.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/core/base/broadcaster.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/core/base/element.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/core/base/event.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/core/base/eventbus.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/core/base/graph.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/core/base/node.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/core/base/pile.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/core/base/processor.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/core/base/progression.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/core/specs/__init__.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/core/specs/adapters/__init__.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/core/specs/adapters/_utils.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/core/specs/adapters/dataclass_field.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/core/specs/adapters/factory.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/core/specs/adapters/pydantic_adapter.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/core/specs/adapters/sql_ddl.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/core/specs/catalog/__init__.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/core/specs/catalog/_audit.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/core/specs/catalog/_common.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/core/specs/catalog/_content.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/core/specs/catalog/_enforcement.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/core/specs/factory.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/core/specs/operable.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/core/specs/protocol.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/core/specs/spec.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/core/types/__init__.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/core/types/_sentinel.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/core/types/base.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/core/types/db_types.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/core/types/identity.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/errors.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/protocols.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/py.typed +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/resource/__init__.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/resource/backend.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/resource/endpoint.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/resource/hook.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/resource/imodel.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/resource/registry.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/resource/utilities/__init__.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/resource/utilities/header_factory.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/resource/utilities/rate_limited_executor.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/resource/utilities/rate_limiter.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/resource/utilities/resilience.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/resource/utilities/token_calculator.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/session/__init__.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/session/constraints.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/session/exchange.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/session/message.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/session/registry.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/utils/__init__.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/utils/_function_arg_parser.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/utils/_hash.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/utils/_json_dump.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/utils/_lazy_init.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/utils/_pythonic_function_call.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/utils/_to_list.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/utils/_to_num.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/utils/_utils.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/utils/concurrency/__init__.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/utils/concurrency/_async_call.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/utils/concurrency/_cancel.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/utils/concurrency/_errors.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/utils/concurrency/_patterns.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/utils/concurrency/_primitives.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/utils/concurrency/_priority_queue.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/utils/concurrency/_resource_tracker.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/utils/concurrency/_run_async.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/utils/concurrency/_task.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/utils/concurrency/_utils.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/utils/display.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/utils/fuzzy/__init__.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/utils/fuzzy/_extract_json.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/utils/fuzzy/_fuzzy_json.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/utils/fuzzy/_fuzzy_match.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/utils/fuzzy/_string_similarity.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/utils/fuzzy/_to_dict.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/utils/schemas/__init__.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/utils/schemas/_breakdown_pydantic_annotation.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/utils/schemas/_formatter.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/utils/schemas/_minimal_yaml.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/utils/schemas/_typescript.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/utils/sql/__init__.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/utils/sql/_sql_validation.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/utils/validators/__init__.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/utils/validators/_validate_image_url.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/work/__init__.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/work/engine.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/work/form.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/work/operations/__init__.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/work/operations/builder.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/work/operations/context.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/work/operations/flow.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/work/operations/node.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/work/operations/registry.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/work/report.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/work/rules/__init__.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/work/rules/common/__init__.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/work/rules/common/boolean.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/work/rules/common/choice.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/work/rules/common/mapping.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/work/rules/common/model.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/work/rules/common/number.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/work/rules/common/string.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/work/rules/registry.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/work/rules/rule.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/work/rules/validator.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/src/krons/work/worker.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/__init__.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/agent/__init__.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/agent/test_mcps_unit.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/agent/test_message.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/agent/test_providers_e2e.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/agent/test_providers_unit.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/agent/test_tool_unit.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/conftest.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/core/__init__.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/core/test_broadcaster.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/core/test_element.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/core/test_error_paths.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/core/test_event.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/core/test_event_status_race.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/core/test_eventbus.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/core/test_flow.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/core/test_flow_edge_cases.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/core/test_graph.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/core/test_graph_event_loop.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/core/test_node.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/core/test_pile.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/core/test_pile_edge_cases.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/core/test_processor.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/core/test_processor_security.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/core/test_progression.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/core/test_progression_edge_cases.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/core/test_serialization_roundtrip.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/core/test_thread_safety_stress.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/operations/__init__.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/operations/test_builder.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/operations/test_flow.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/operations/test_node.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/operations/test_op_flow.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/operations/test_op_node.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/operations/test_registry.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/resource/__init__.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/resource/test_backend.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/resource/test_endpoint.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/resource/test_hook.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/resource/test_imodel.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/resource/test_registry.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/resource/utilities/__init__.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/resource/utilities/test_header_factory.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/resource/utilities/test_rate_limiter.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/resource/utilities/test_resilience.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/resource/utilities/test_resilience_retry.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/rules/__init__.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/rules/test_base.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/rules/test_builtin.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/rules/test_registry.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/rules/test_rule_params_edge_cases.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/rules/test_rules_comprehensive.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/rules/test_validator.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/session/__init__.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/session/test_exchange.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/session/test_message.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/session/test_message_edge_cases.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/session/test_session.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/session/test_session_edge_cases.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/specs/__init__.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/specs/test_catalog.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/specs/test_factory.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/test_protocols.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/types/__init__.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/types/conftest.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/types/spec_adapters/__init__.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/types/spec_adapters/test_adapters_py311.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/types/spec_adapters/test_protocol.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/types/spec_adapters/test_pydantic_field.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/types/spec_adapters/test_sql_ddl_specs.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/types/test_db_types.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/types/test_identity.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/types/test_model.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/types/test_operable.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/types/test_sentinel.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/types/test_spec.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/types/test_types.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/types/test_types_py311.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/utils/__init__.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/utils/concurrency/__init__.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/utils/concurrency/test_async_call.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/utils/concurrency/test_cancel.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/utils/concurrency/test_errors.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/utils/concurrency/test_patterns.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/utils/concurrency/test_primitives.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/utils/concurrency/test_priority_queue.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/utils/concurrency/test_run_async.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/utils/concurrency/test_task.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/utils/concurrency/test_utils.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/utils/test_extract_json.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/utils/test_fuzzy_json.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/utils/test_fuzzy_match.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/utils/test_hash.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/utils/test_json_dump.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/utils/test_lazy_init.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/utils/test_sql_validation.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/utils/test_string_similarity.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/utils/test_to_dict.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/utils/test_to_list.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/utils/test_to_num.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/utils/test_utils.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/work/__init__.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/work/test_engine.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/work/test_form.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/work/test_report.py +0 -0
- {krons-0.2.2 → krons-0.2.3}/tests/work/test_worker.py +0 -0
krons-0.2.3/CLAUDE.md
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
# krons - Spec-Based Composable Framework
|
|
2
|
+
|
|
3
|
+
## Quick Reference
|
|
4
|
+
|
|
5
|
+
```text
|
|
6
|
+
krons/
|
|
7
|
+
├── core/ # Element, Node, Flow, Graph, Pile, Event
|
|
8
|
+
├── session/ # Session, Branch, Message (conversation orchestration)
|
|
9
|
+
├── resource/ # iModel, Endpoint, HookRegistry (API backends)
|
|
10
|
+
├── agent/ # Operations pipeline (generate → parse → structure → operate → react)
|
|
11
|
+
├── work/ # Report, Worker, Form (workflow orchestration)
|
|
12
|
+
└── utils/ # JSON, concurrency, display helpers
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Core Primitives
|
|
16
|
+
|
|
17
|
+
### Element — Base identity class
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
from krons.core import Element
|
|
21
|
+
|
|
22
|
+
class MyEntity(Element):
|
|
23
|
+
name: str
|
|
24
|
+
|
|
25
|
+
e = MyEntity(name="test")
|
|
26
|
+
e.id # UUID (frozen)
|
|
27
|
+
e.created_at # datetime (frozen)
|
|
28
|
+
e.to_dict(mode="json") # Serialization with kron_class for polymorphic restore
|
|
29
|
+
Element.from_dict(data) # Polymorphic deserialization
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### Pile — Thread-safe typed collection
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
from krons.core import Pile
|
|
36
|
+
|
|
37
|
+
pile = Pile[MyEntity](item_type={MyEntity})
|
|
38
|
+
pile.add(entity)
|
|
39
|
+
pile.get(uuid_or_str)
|
|
40
|
+
pile[0] # By index
|
|
41
|
+
pile[lambda x: x.name] # Filter
|
|
42
|
+
async with pile: # Async lock
|
|
43
|
+
...
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Node — Polymorphic content container
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
from krons.core import Node, create_node
|
|
50
|
+
|
|
51
|
+
# Dynamic node
|
|
52
|
+
node = Node(content={"key": "value"})
|
|
53
|
+
|
|
54
|
+
# Typed node with DB features
|
|
55
|
+
MyNode = create_node("MyNode", content=MyContent, flatten_content=True)
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Session — Conversation Orchestration
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
from krons.session import Session, SessionConfig
|
|
62
|
+
|
|
63
|
+
session = Session(config=SessionConfig(
|
|
64
|
+
default_branch_name="main",
|
|
65
|
+
default_gen_model="gpt-4",
|
|
66
|
+
log_persist_dir="./logs", # Enable persistence (None = disabled)
|
|
67
|
+
log_auto_save_on_exit=True, # atexit handler
|
|
68
|
+
))
|
|
69
|
+
|
|
70
|
+
# Register model
|
|
71
|
+
session.resources.register(imodel)
|
|
72
|
+
|
|
73
|
+
# Execute operations
|
|
74
|
+
op = await session.conduct("generate", params=GenerateParams(...))
|
|
75
|
+
op = await session.conduct("structure", params=StructureParams(...))
|
|
76
|
+
|
|
77
|
+
# Access messages
|
|
78
|
+
session.messages # Pile[Message]
|
|
79
|
+
session.dump() # Sync dump full session to JSON
|
|
80
|
+
await session.adump() # Async dump
|
|
81
|
+
|
|
82
|
+
# Restore from dump
|
|
83
|
+
restored = Session.from_dict(data) # Then re-register resources
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Resource — API Backends
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
from krons.resource import iModel, Endpoint, EndpointConfig
|
|
90
|
+
|
|
91
|
+
config = EndpointConfig(
|
|
92
|
+
name="gpt-4",
|
|
93
|
+
provider="openai",
|
|
94
|
+
endpoint="chat/completions",
|
|
95
|
+
base_url="https://api.openai.com/v1",
|
|
96
|
+
api_key="...",
|
|
97
|
+
)
|
|
98
|
+
model = iModel(backend=Endpoint(config=config))
|
|
99
|
+
calling = await model.invoke(messages=[...])
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Agent Operations
|
|
103
|
+
|
|
104
|
+
Pipeline: `generate → parse → structure → operate → react`
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
from krons.agent.operations import GenerateParams, StructureParams
|
|
108
|
+
|
|
109
|
+
# Generate raw LLM response
|
|
110
|
+
op = await session.conduct("generate", params=GenerateParams(
|
|
111
|
+
primary="Your prompt",
|
|
112
|
+
imodel="gpt-4",
|
|
113
|
+
))
|
|
114
|
+
|
|
115
|
+
# Structured output with validation
|
|
116
|
+
op = await session.conduct("structure", params=StructureParams(
|
|
117
|
+
generate_params=GenerateParams(primary="...", request_model=MyModel),
|
|
118
|
+
validator=Validator(),
|
|
119
|
+
operable=Operable.from_structure(MyModel),
|
|
120
|
+
))
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Work — Workflow Orchestration
|
|
124
|
+
|
|
125
|
+
### Report (declarative workflow)
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
from krons.work import Report
|
|
129
|
+
|
|
130
|
+
class MyReport(Report):
|
|
131
|
+
result: str | None = None
|
|
132
|
+
assignment: str = "input -> result"
|
|
133
|
+
form_assignments: list[str] = [
|
|
134
|
+
"step1: input -> intermediate",
|
|
135
|
+
"step2: intermediate -> result",
|
|
136
|
+
]
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Worker (execution capability)
|
|
140
|
+
|
|
141
|
+
```python
|
|
142
|
+
from krons.work import Worker, work
|
|
143
|
+
|
|
144
|
+
class MyWorker(Worker):
|
|
145
|
+
name = "processor"
|
|
146
|
+
|
|
147
|
+
@work(assignment="input -> output")
|
|
148
|
+
async def process(self, input, **kwargs):
|
|
149
|
+
return transform(input)
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Protocols
|
|
153
|
+
|
|
154
|
+
```python
|
|
155
|
+
from krons.protocols import implements, Serializable
|
|
156
|
+
|
|
157
|
+
@implements(Serializable, signature_check="error")
|
|
158
|
+
class MyClass:
|
|
159
|
+
def to_dict(self, **kwargs): ...
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## Testing
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
uv run pytest tests/ -q # All tests
|
|
166
|
+
uv run pytest tests/core/ -xvs # Core module verbose
|
|
167
|
+
uv run pytest -k "test_session" -v # Pattern match
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
```python
|
|
171
|
+
import pytest
|
|
172
|
+
|
|
173
|
+
@pytest.mark.anyio
|
|
174
|
+
async def test_async():
|
|
175
|
+
result = await some_call()
|
|
176
|
+
assert result
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Code Style
|
|
180
|
+
|
|
181
|
+
- Python 3.11+, Pydantic v2, anyio
|
|
182
|
+
- `uv run ruff check src/ --fix && uv run ruff format src/`
|
|
183
|
+
- Type hints required on public APIs
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Copyright (c) 2025 - 2026, HaiyangLi <quantocean.li at gmail dot com>
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
"""krons Usage Examples
|
|
5
|
+
|
|
6
|
+
This module provides practical, runnable examples demonstrating krons patterns
|
|
7
|
+
with Claude Code integration.
|
|
8
|
+
|
|
9
|
+
Multi-Agent Patterns (parallel Claude Code):
|
|
10
|
+
code_review_panel - 3 specialist reviewers + moderator synthesis
|
|
11
|
+
tech_debate - Adversarial debate with advocate/skeptic/pragmatist + judge
|
|
12
|
+
|
|
13
|
+
Worker Patterns (declarative workflows):
|
|
14
|
+
validation_loop - Self-correcting LLM generation with bounded retries
|
|
15
|
+
codegen_pipeline - Code generation with error recovery workflow
|
|
16
|
+
research_agent - Adaptive depth research with confidence-based routing
|
|
17
|
+
|
|
18
|
+
Exchange Patterns (async message passing):
|
|
19
|
+
pipeline_router - Multi-stage data processing pipeline
|
|
20
|
+
event_sourcing - Event persistence, snapshots, and replay
|
|
21
|
+
|
|
22
|
+
Specs Patterns (dynamic schema composition):
|
|
23
|
+
dynamic_response - Runtime Pydantic model generation with validators
|
|
24
|
+
|
|
25
|
+
Combined Patterns:
|
|
26
|
+
multi_agent_orchestration - Supervisor-worker coordination via Exchange + Worker
|
|
27
|
+
|
|
28
|
+
Run any example:
|
|
29
|
+
uv run python examples/code_review_panel.py
|
|
30
|
+
uv run python examples/tech_debate.py
|
|
31
|
+
uv run python examples/validation_loop.py
|
|
32
|
+
# etc.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
__all__ = [
|
|
36
|
+
# Multi-agent patterns (featured)
|
|
37
|
+
"code_review_panel",
|
|
38
|
+
"tech_debate",
|
|
39
|
+
# Worker patterns
|
|
40
|
+
"validation_loop",
|
|
41
|
+
"codegen_pipeline",
|
|
42
|
+
"research_agent",
|
|
43
|
+
# Exchange patterns
|
|
44
|
+
"pipeline_router",
|
|
45
|
+
"event_sourcing",
|
|
46
|
+
# Specs patterns
|
|
47
|
+
"dynamic_response",
|
|
48
|
+
# Combined patterns
|
|
49
|
+
"multi_agent_orchestration",
|
|
50
|
+
]
|
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
# Copyright (c) 2025 - 2026, HaiyangLi <quantocean.li at gmail dot com>
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
"""Multi-Agent Code Review Panel with Claude Code.
|
|
5
|
+
|
|
6
|
+
A panel of specialized reviewers analyze code from different perspectives,
|
|
7
|
+
then a moderator synthesizes their findings into actionable feedback.
|
|
8
|
+
|
|
9
|
+
Architecture:
|
|
10
|
+
Moderator (orchestrator)
|
|
11
|
+
└─> SecurityReviewer ─┐
|
|
12
|
+
└─> PerformanceReviewer ├─> Moderator synthesizes
|
|
13
|
+
└─> ArchitectureReviewer┘
|
|
14
|
+
|
|
15
|
+
Features:
|
|
16
|
+
- Structured outputs with Pydantic models
|
|
17
|
+
- Parallel fan-out to specialist agents
|
|
18
|
+
- Severity-scored findings
|
|
19
|
+
- Synthesized final verdict
|
|
20
|
+
|
|
21
|
+
Usage:
|
|
22
|
+
uv run python examples/code_review_panel.py
|
|
23
|
+
uv run python examples/code_review_panel.py --file path/to/code.py
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
import sys
|
|
29
|
+
from enum import Enum
|
|
30
|
+
|
|
31
|
+
import anyio
|
|
32
|
+
from pydantic import BaseModel, Field
|
|
33
|
+
|
|
34
|
+
from krons.agent.operations import GenerateParams, ReturnAs
|
|
35
|
+
from krons.agent.providers.claude_code import (
|
|
36
|
+
ClaudeCodeEndpoint,
|
|
37
|
+
create_claude_code_config,
|
|
38
|
+
)
|
|
39
|
+
from krons.resource import iModel
|
|
40
|
+
from krons.session import Session, SessionConfig
|
|
41
|
+
from krons.utils.display import Timer, as_readable, display, phase, status
|
|
42
|
+
from krons.utils.fuzzy import extract_json, fuzzy_validate_mapping
|
|
43
|
+
|
|
44
|
+
CC_WORKSPACE = ".khive/examples/code_review_panel"
|
|
45
|
+
VERBOSE = True
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
# ---------------------------------------------------------------------------
|
|
49
|
+
# Structured Output Models
|
|
50
|
+
# ---------------------------------------------------------------------------
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class Severity(str, Enum):
|
|
54
|
+
"""Issue severity levels."""
|
|
55
|
+
|
|
56
|
+
CRITICAL = "critical"
|
|
57
|
+
HIGH = "high"
|
|
58
|
+
MEDIUM = "medium"
|
|
59
|
+
LOW = "low"
|
|
60
|
+
INFO = "info"
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class ReviewFinding(BaseModel):
|
|
64
|
+
"""A single finding from a reviewer."""
|
|
65
|
+
|
|
66
|
+
title: str = Field(description="Short title of the finding")
|
|
67
|
+
severity: Severity = Field(description="Severity level")
|
|
68
|
+
description: str = Field(description="Detailed description of the issue")
|
|
69
|
+
suggestion: str = Field(description="How to fix or improve")
|
|
70
|
+
line_hint: str | None = Field(
|
|
71
|
+
default=None, description="Relevant code location hint"
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class SpecialistReview(BaseModel):
|
|
76
|
+
"""Complete review from a specialist."""
|
|
77
|
+
|
|
78
|
+
perspective: str = Field(
|
|
79
|
+
description="The review perspective (security/performance/architecture)"
|
|
80
|
+
)
|
|
81
|
+
summary: str = Field(description="Executive summary of findings")
|
|
82
|
+
findings: list[ReviewFinding] = Field(description="List of specific findings")
|
|
83
|
+
score: int = Field(ge=0, le=100, description="Overall score 0-100")
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class PanelVerdict(BaseModel):
|
|
87
|
+
"""Final synthesized verdict from the panel."""
|
|
88
|
+
|
|
89
|
+
overall_assessment: str = Field(description="High-level assessment of code quality")
|
|
90
|
+
critical_issues: list[str] = Field(description="Must-fix issues before merge")
|
|
91
|
+
recommendations: list[str] = Field(description="Suggested improvements")
|
|
92
|
+
approval_status: str = Field(description="APPROVED, NEEDS_CHANGES, or REJECTED")
|
|
93
|
+
confidence: float = Field(ge=0, le=1, description="Panel confidence in verdict")
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
# ---------------------------------------------------------------------------
|
|
97
|
+
# Claude Code Factory
|
|
98
|
+
# ---------------------------------------------------------------------------
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def create_cc(
|
|
102
|
+
name: str, subdir: str, system_prompt: str | None = None, **kwargs
|
|
103
|
+
) -> iModel:
|
|
104
|
+
"""Create a Claude Code iModel with optional system prompt."""
|
|
105
|
+
config = create_claude_code_config(name=name)
|
|
106
|
+
config.update({"ws": f"{CC_WORKSPACE}/{subdir}", "max_turns": 3, **kwargs})
|
|
107
|
+
if system_prompt:
|
|
108
|
+
config["system_prompt"] = system_prompt
|
|
109
|
+
endpoint = ClaudeCodeEndpoint(config=config)
|
|
110
|
+
return iModel(backend=endpoint)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
# ---------------------------------------------------------------------------
|
|
114
|
+
# Reviewer Personas
|
|
115
|
+
# ---------------------------------------------------------------------------
|
|
116
|
+
|
|
117
|
+
SECURITY_PERSONA = """\
|
|
118
|
+
You are a senior security engineer conducting a security-focused code review.
|
|
119
|
+
Focus on: input validation, injection vulnerabilities, authentication/authorization,
|
|
120
|
+
secrets handling, cryptography usage, and OWASP Top 10 issues.
|
|
121
|
+
Be thorough but practical - prioritize real risks over theoretical concerns."""
|
|
122
|
+
|
|
123
|
+
PERFORMANCE_PERSONA = """\
|
|
124
|
+
You are a performance engineer reviewing code for efficiency.
|
|
125
|
+
Focus on: algorithmic complexity, memory usage, I/O patterns, caching opportunities,
|
|
126
|
+
unnecessary allocations, blocking operations, and scalability concerns.
|
|
127
|
+
Consider both micro-optimizations and architectural performance."""
|
|
128
|
+
|
|
129
|
+
ARCHITECTURE_PERSONA = """\
|
|
130
|
+
You are a software architect reviewing code structure and design.
|
|
131
|
+
Focus on: SOLID principles, separation of concerns, dependency management,
|
|
132
|
+
testability, maintainability, API design, and code organization.
|
|
133
|
+
Balance pragmatism with clean architecture principles."""
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
# ---------------------------------------------------------------------------
|
|
137
|
+
# Prompts
|
|
138
|
+
# ---------------------------------------------------------------------------
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def make_review_prompt(code: str, perspective: str) -> str:
|
|
142
|
+
"""Create a review prompt for a specific perspective."""
|
|
143
|
+
return f"""\
|
|
144
|
+
Review the following code from a {perspective} perspective.
|
|
145
|
+
|
|
146
|
+
```python
|
|
147
|
+
{code}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Provide your review as structured JSON matching this schema:
|
|
151
|
+
- perspective: "{perspective}"
|
|
152
|
+
- summary: Executive summary (2-3 sentences)
|
|
153
|
+
- findings: List of findings, each with:
|
|
154
|
+
- title: Short issue title
|
|
155
|
+
- severity: one of "critical", "high", "medium", "low", "info"
|
|
156
|
+
- description: What's wrong and why it matters
|
|
157
|
+
- suggestion: How to fix it
|
|
158
|
+
- line_hint: Approximate location (optional)
|
|
159
|
+
- score: Overall score 0-100 for this perspective
|
|
160
|
+
|
|
161
|
+
Be specific and actionable. Return valid JSON only."""
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
SYNTHESIS_PROMPT = """\
|
|
165
|
+
You are the panel moderator. Synthesize the specialist reviews into a final verdict.
|
|
166
|
+
|
|
167
|
+
Reviews from specialists:
|
|
168
|
+
{reviews}
|
|
169
|
+
|
|
170
|
+
Provide your verdict as structured JSON:
|
|
171
|
+
- overall_assessment: 2-3 sentence summary of code quality
|
|
172
|
+
- critical_issues: List of must-fix issues (empty if none)
|
|
173
|
+
- recommendations: List of suggested improvements
|
|
174
|
+
- approval_status: "APPROVED", "NEEDS_CHANGES", or "REJECTED"
|
|
175
|
+
- confidence: 0.0 to 1.0 confidence in this verdict
|
|
176
|
+
|
|
177
|
+
Consider all perspectives equally. Be decisive but fair. Return valid JSON only."""
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
# ---------------------------------------------------------------------------
|
|
181
|
+
# Parsing Helpers
|
|
182
|
+
# ---------------------------------------------------------------------------
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def parse_review(text: str, perspective: str) -> SpecialistReview:
|
|
186
|
+
"""Parse a specialist review from LLM output."""
|
|
187
|
+
extracted = extract_json(text, fuzzy_parse=True)
|
|
188
|
+
if not extracted:
|
|
189
|
+
# Fallback for unparseable responses
|
|
190
|
+
return SpecialistReview(
|
|
191
|
+
perspective=perspective,
|
|
192
|
+
summary="Review could not be parsed",
|
|
193
|
+
findings=[],
|
|
194
|
+
score=50,
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
block = extracted[0] if isinstance(extracted, list) else extracted
|
|
198
|
+
target_keys = list(SpecialistReview.model_fields.keys())
|
|
199
|
+
validated = fuzzy_validate_mapping(block, target_keys)
|
|
200
|
+
validated["perspective"] = perspective # Ensure correct perspective
|
|
201
|
+
return SpecialistReview.model_validate(validated)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def parse_verdict(text: str) -> PanelVerdict:
|
|
205
|
+
"""Parse the final verdict from moderator."""
|
|
206
|
+
extracted = extract_json(text, fuzzy_parse=True)
|
|
207
|
+
if not extracted:
|
|
208
|
+
return PanelVerdict(
|
|
209
|
+
overall_assessment="Verdict could not be parsed",
|
|
210
|
+
critical_issues=[],
|
|
211
|
+
recommendations=[],
|
|
212
|
+
approval_status="NEEDS_CHANGES",
|
|
213
|
+
confidence=0.5,
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
block = extracted[0] if isinstance(extracted, list) else extracted
|
|
217
|
+
target_keys = list(PanelVerdict.model_fields.keys())
|
|
218
|
+
validated = fuzzy_validate_mapping(block, target_keys)
|
|
219
|
+
return PanelVerdict.model_validate(validated)
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
# ---------------------------------------------------------------------------
|
|
223
|
+
# Sample Code for Review
|
|
224
|
+
# ---------------------------------------------------------------------------
|
|
225
|
+
|
|
226
|
+
SAMPLE_CODE = """\
|
|
227
|
+
import sqlite3
|
|
228
|
+
import hashlib
|
|
229
|
+
from flask import Flask, request, jsonify
|
|
230
|
+
|
|
231
|
+
app = Flask(__name__)
|
|
232
|
+
db = sqlite3.connect("users.db", check_same_thread=False)
|
|
233
|
+
|
|
234
|
+
@app.route("/login", methods=["POST"])
|
|
235
|
+
def login():
|
|
236
|
+
username = request.form["username"]
|
|
237
|
+
password = request.form["password"]
|
|
238
|
+
|
|
239
|
+
# Hash password
|
|
240
|
+
pw_hash = hashlib.md5(password.encode()).hexdigest()
|
|
241
|
+
|
|
242
|
+
# Check credentials
|
|
243
|
+
cursor = db.cursor()
|
|
244
|
+
query = f"SELECT * FROM users WHERE username='{username}' AND password='{pw_hash}'"
|
|
245
|
+
result = cursor.execute(query).fetchone()
|
|
246
|
+
|
|
247
|
+
if result:
|
|
248
|
+
return jsonify({"status": "success", "user": result})
|
|
249
|
+
return jsonify({"status": "failed"})
|
|
250
|
+
|
|
251
|
+
@app.route("/users")
|
|
252
|
+
def get_users():
|
|
253
|
+
cursor = db.cursor()
|
|
254
|
+
users = cursor.execute("SELECT * FROM users").fetchall()
|
|
255
|
+
return jsonify(users)
|
|
256
|
+
|
|
257
|
+
def process_data(items):
|
|
258
|
+
result = []
|
|
259
|
+
for item in items:
|
|
260
|
+
for i in range(len(items)):
|
|
261
|
+
if items[i] == item:
|
|
262
|
+
result.append(item * 2)
|
|
263
|
+
return list(set(result))
|
|
264
|
+
|
|
265
|
+
if __name__ == "__main__":
|
|
266
|
+
app.run(debug=True, host="0.0.0.0")
|
|
267
|
+
"""
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
# ---------------------------------------------------------------------------
|
|
271
|
+
# Main
|
|
272
|
+
# ---------------------------------------------------------------------------
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
async def main(code: str | None = None):
|
|
276
|
+
"""Run the code review panel."""
|
|
277
|
+
code_to_review = code or SAMPLE_CODE
|
|
278
|
+
|
|
279
|
+
print("=" * 70)
|
|
280
|
+
print(" Multi-Agent Code Review Panel")
|
|
281
|
+
print("=" * 70)
|
|
282
|
+
print()
|
|
283
|
+
|
|
284
|
+
# --- Setup Session ---
|
|
285
|
+
phase("Setting up review panel")
|
|
286
|
+
|
|
287
|
+
moderator = create_cc("moderator", "moderator")
|
|
288
|
+
session = Session(
|
|
289
|
+
config=SessionConfig(
|
|
290
|
+
default_branch_name="panel",
|
|
291
|
+
default_gen_model="moderator",
|
|
292
|
+
shared_resources={"moderator"},
|
|
293
|
+
)
|
|
294
|
+
)
|
|
295
|
+
session.resources.register(moderator)
|
|
296
|
+
|
|
297
|
+
panel_branch = session.default_branch
|
|
298
|
+
status(f"Panel ready: {len(session.resources)} resources")
|
|
299
|
+
|
|
300
|
+
# --- Phase 1: Parallel Specialist Reviews ---
|
|
301
|
+
phase("Phase 1: Specialist Reviews (parallel)")
|
|
302
|
+
|
|
303
|
+
reviewers = [
|
|
304
|
+
("security", SECURITY_PERSONA),
|
|
305
|
+
("performance", PERFORMANCE_PERSONA),
|
|
306
|
+
("architecture", ARCHITECTURE_PERSONA),
|
|
307
|
+
]
|
|
308
|
+
|
|
309
|
+
reviews: dict[str, SpecialistReview] = {}
|
|
310
|
+
|
|
311
|
+
async def run_review(perspective: str, persona: str) -> None:
|
|
312
|
+
name = f"reviewer_{perspective}"
|
|
313
|
+
reviewer = create_cc(name, name, system_prompt=persona)
|
|
314
|
+
session.resources.register(reviewer, update=True)
|
|
315
|
+
branch = session.create_branch(name=name, resources={name})
|
|
316
|
+
|
|
317
|
+
prompt = make_review_prompt(code_to_review, perspective)
|
|
318
|
+
|
|
319
|
+
with Timer() as t:
|
|
320
|
+
op = await session.conduct(
|
|
321
|
+
"generate",
|
|
322
|
+
branch,
|
|
323
|
+
GenerateParams(
|
|
324
|
+
primary=prompt,
|
|
325
|
+
request_model=SpecialistReview,
|
|
326
|
+
imodel=name,
|
|
327
|
+
return_as=ReturnAs.TEXT,
|
|
328
|
+
),
|
|
329
|
+
verbose=VERBOSE,
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
review = parse_review(op.execution.response, perspective)
|
|
333
|
+
reviews[perspective] = review
|
|
334
|
+
|
|
335
|
+
status(
|
|
336
|
+
f"[{perspective.upper()}] Score: {review.score}/100, "
|
|
337
|
+
f"Findings: {len(review.findings)} ({t.elapsed:.1f}s)",
|
|
338
|
+
style="success" if review.score >= 70 else "warning",
|
|
339
|
+
)
|
|
340
|
+
|
|
341
|
+
async with anyio.create_task_group() as tg:
|
|
342
|
+
for perspective, persona in reviewers:
|
|
343
|
+
tg.start_soon(run_review, perspective, persona)
|
|
344
|
+
|
|
345
|
+
# --- Display Individual Reviews ---
|
|
346
|
+
print()
|
|
347
|
+
for perspective, review in reviews.items():
|
|
348
|
+
print(f"┌─ {perspective.upper()} REVIEW (Score: {review.score}/100)")
|
|
349
|
+
print(f"│ {review.summary}")
|
|
350
|
+
for finding in review.findings[:3]: # Show top 3 findings
|
|
351
|
+
severity_icon = {
|
|
352
|
+
Severity.CRITICAL: "🔴",
|
|
353
|
+
Severity.HIGH: "🟠",
|
|
354
|
+
Severity.MEDIUM: "🟡",
|
|
355
|
+
Severity.LOW: "🟢",
|
|
356
|
+
Severity.INFO: "ℹ️",
|
|
357
|
+
}.get(finding.severity, "•")
|
|
358
|
+
print(f"│ {severity_icon} [{finding.severity.value}] {finding.title}")
|
|
359
|
+
if len(review.findings) > 3:
|
|
360
|
+
print(f"│ ... and {len(review.findings) - 3} more findings")
|
|
361
|
+
print("└" + "─" * 60)
|
|
362
|
+
print()
|
|
363
|
+
|
|
364
|
+
# --- Phase 2: Moderator Synthesis ---
|
|
365
|
+
phase("Phase 2: Panel Synthesis")
|
|
366
|
+
|
|
367
|
+
reviews_text = "\n\n".join(
|
|
368
|
+
f"=== {p.upper()} REVIEWER (Score: {r.score}/100) ===\n"
|
|
369
|
+
f"Summary: {r.summary}\n"
|
|
370
|
+
f"Findings:\n"
|
|
371
|
+
+ "\n".join(
|
|
372
|
+
f"- [{f.severity.value}] {f.title}: {f.description}" for f in r.findings
|
|
373
|
+
)
|
|
374
|
+
for p, r in reviews.items()
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
synthesis_prompt = SYNTHESIS_PROMPT.format(reviews=reviews_text)
|
|
378
|
+
|
|
379
|
+
with Timer() as t:
|
|
380
|
+
synth_op = await session.conduct(
|
|
381
|
+
"generate",
|
|
382
|
+
panel_branch,
|
|
383
|
+
GenerateParams(
|
|
384
|
+
primary=synthesis_prompt,
|
|
385
|
+
request_model=PanelVerdict,
|
|
386
|
+
imodel="moderator",
|
|
387
|
+
return_as=ReturnAs.TEXT,
|
|
388
|
+
),
|
|
389
|
+
verbose=VERBOSE,
|
|
390
|
+
)
|
|
391
|
+
|
|
392
|
+
verdict = parse_verdict(synth_op.execution.response)
|
|
393
|
+
status(f"Synthesis complete ({t.elapsed:.1f}s)")
|
|
394
|
+
|
|
395
|
+
# --- Final Verdict ---
|
|
396
|
+
print()
|
|
397
|
+
print("=" * 70)
|
|
398
|
+
print(" PANEL VERDICT")
|
|
399
|
+
print("=" * 70)
|
|
400
|
+
print()
|
|
401
|
+
|
|
402
|
+
status_icon = {
|
|
403
|
+
"APPROVED": "✅",
|
|
404
|
+
"NEEDS_CHANGES": "⚠️",
|
|
405
|
+
"REJECTED": "❌",
|
|
406
|
+
}.get(verdict.approval_status, "❓")
|
|
407
|
+
|
|
408
|
+
print(f"Status: {status_icon} {verdict.approval_status}")
|
|
409
|
+
print(f"Confidence: {verdict.confidence:.0%}")
|
|
410
|
+
print()
|
|
411
|
+
print("Assessment:")
|
|
412
|
+
print(f" {verdict.overall_assessment}")
|
|
413
|
+
print()
|
|
414
|
+
|
|
415
|
+
if verdict.critical_issues:
|
|
416
|
+
print("Critical Issues (must fix):")
|
|
417
|
+
for issue in verdict.critical_issues:
|
|
418
|
+
print(f" 🔴 {issue}")
|
|
419
|
+
print()
|
|
420
|
+
|
|
421
|
+
if verdict.recommendations:
|
|
422
|
+
print("Recommendations:")
|
|
423
|
+
for rec in verdict.recommendations:
|
|
424
|
+
print(f" 💡 {rec}")
|
|
425
|
+
print()
|
|
426
|
+
|
|
427
|
+
# --- Summary Stats ---
|
|
428
|
+
avg_score = sum(r.score for r in reviews.values()) / len(reviews)
|
|
429
|
+
total_findings = sum(len(r.findings) for r in reviews.values())
|
|
430
|
+
critical_count = sum(
|
|
431
|
+
1
|
|
432
|
+
for r in reviews.values()
|
|
433
|
+
for f in r.findings
|
|
434
|
+
if f.severity == Severity.CRITICAL
|
|
435
|
+
)
|
|
436
|
+
|
|
437
|
+
print("─" * 70)
|
|
438
|
+
print(f"Average Score: {avg_score:.0f}/100")
|
|
439
|
+
print(f"Total Findings: {total_findings}")
|
|
440
|
+
print(f"Critical Issues: {critical_count}")
|
|
441
|
+
print("─" * 70)
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
if __name__ == "__main__":
|
|
445
|
+
code = None
|
|
446
|
+
if "--file" in sys.argv:
|
|
447
|
+
idx = sys.argv.index("--file")
|
|
448
|
+
if idx + 1 < len(sys.argv):
|
|
449
|
+
with open(sys.argv[idx + 1]) as f:
|
|
450
|
+
code = f.read()
|
|
451
|
+
|
|
452
|
+
anyio.run(main, code)
|