devcouncil 0.3.1 → 0.4.1
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.
- package/README.md +92 -33
- package/package.json +6 -2
- package/packages/codeintel-grammars/hatch_build.py +43 -0
- package/packages/codeintel-grammars/pyproject.toml +16 -0
- package/packages/codeintel-grammars/src/devcouncil_codeintel_grammars/__init__.py +93 -0
- package/pyproject.toml +99 -4
- package/src/devcouncil/app/config.py +512 -20
- package/src/devcouncil/app/events.py +4 -23
- package/src/devcouncil/app/orchestrator.py +5 -0
- package/src/devcouncil/app/run_context.py +3 -3
- package/src/devcouncil/assets/__init__.py +4 -1
- package/src/devcouncil/assets/vendor/force-graph.min.js +5 -0
- package/src/devcouncil/campaign/__init__.py +71 -0
- package/src/devcouncil/campaign/bloom.py +137 -0
- package/src/devcouncil/campaign/dashboard.py +123 -0
- package/src/devcouncil/campaign/mailbox.py +305 -0
- package/src/devcouncil/campaign/notify.py +91 -0
- package/src/devcouncil/campaign/orchestrator.py +592 -0
- package/src/devcouncil/campaign/prompts/coordinator.md +29 -0
- package/src/devcouncil/campaign/prompts/director.md +21 -0
- package/src/devcouncil/campaign/prompts/protocol.md +46 -0
- package/src/devcouncil/campaign/prompts/reviewer.md +24 -0
- package/src/devcouncil/campaign/prompts/worker.md +24 -0
- package/src/devcouncil/campaign/roles.py +202 -0
- package/src/devcouncil/campaign/watcher.py +153 -0
- package/src/devcouncil/cli/commands/agents.py +24 -17
- package/src/devcouncil/cli/commands/artifacts.py +36 -27
- package/src/devcouncil/cli/commands/ast.py +12 -3
- package/src/devcouncil/cli/commands/baseline.py +21 -12
- package/src/devcouncil/cli/commands/boot.py +218 -0
- package/src/devcouncil/cli/commands/campaign.py +302 -0
- package/src/devcouncil/cli/commands/check.py +225 -12
- package/src/devcouncil/cli/commands/config.py +221 -74
- package/src/devcouncil/cli/commands/cost.py +137 -28
- package/src/devcouncil/cli/commands/dashboard.py +12 -4
- package/src/devcouncil/cli/commands/debug_cmd.py +249 -0
- package/src/devcouncil/cli/commands/design.py +27 -17
- package/src/devcouncil/cli/commands/doctor.py +790 -8
- package/src/devcouncil/cli/commands/evidence.py +41 -20
- package/src/devcouncil/cli/commands/export.py +73 -0
- package/src/devcouncil/cli/commands/gaps.py +175 -0
- package/src/devcouncil/cli/commands/gated_write.py +76 -0
- package/src/devcouncil/cli/commands/go.py +220 -68
- package/src/devcouncil/cli/commands/graph_cmd.py +1192 -0
- package/src/devcouncil/cli/commands/handoff.py +45 -34
- package/src/devcouncil/cli/commands/hook.py +630 -85
- package/src/devcouncil/cli/commands/init.py +89 -30
- package/src/devcouncil/cli/commands/integrate.py +296 -1385
- package/src/devcouncil/cli/commands/lease.py +120 -0
- package/src/devcouncil/cli/commands/logs.py +12 -5
- package/src/devcouncil/cli/commands/lsp.py +40 -5
- package/src/devcouncil/cli/commands/map.py +317 -74
- package/src/devcouncil/cli/commands/mcp_server.py +12 -2
- package/src/devcouncil/cli/commands/okf.py +44 -6
- package/src/devcouncil/cli/commands/plan.py +184 -69
- package/src/devcouncil/cli/commands/prompt.py +26 -17
- package/src/devcouncil/cli/commands/provenance.py +79 -0
- package/src/devcouncil/cli/commands/repair.py +60 -49
- package/src/devcouncil/cli/commands/report.py +148 -40
- package/src/devcouncil/cli/commands/requirements.py +104 -0
- package/src/devcouncil/cli/commands/reset_demo_state.py +13 -4
- package/src/devcouncil/cli/commands/rollback.py +46 -35
- package/src/devcouncil/cli/commands/run.py +173 -8
- package/src/devcouncil/cli/commands/runs.py +298 -68
- package/src/devcouncil/cli/commands/scaffold.py +33 -12
- package/src/devcouncil/cli/commands/semantic.py +29 -14
- package/src/devcouncil/cli/commands/setup.py +103 -93
- package/src/devcouncil/cli/commands/shell.py +51 -42
- package/src/devcouncil/cli/commands/show.py +56 -42
- package/src/devcouncil/cli/commands/skills.py +29 -20
- package/src/devcouncil/cli/commands/status.py +80 -67
- package/src/devcouncil/cli/commands/task_gate.py +295 -0
- package/src/devcouncil/cli/commands/tasks.py +248 -19
- package/src/devcouncil/cli/commands/trace.py +14 -8
- package/src/devcouncil/cli/commands/verify.py +33 -8
- package/src/devcouncil/cli/commands/version.py +14 -6
- package/src/devcouncil/cli/commands/watch.py +56 -40
- package/src/devcouncil/cli/commands/watch_fs.py +30 -19
- package/src/devcouncil/cli/commands/wiki.py +278 -0
- package/src/devcouncil/cli/main.py +58 -1
- package/src/devcouncil/codeintel/__init__.py +16 -0
- package/src/devcouncil/codeintel/build_control.py +429 -0
- package/src/devcouncil/codeintel/build_worker.py +78 -0
- package/src/devcouncil/codeintel/debug/__init__.py +17 -0
- package/src/devcouncil/codeintel/debug/broker.py +114 -0
- package/src/devcouncil/codeintel/debug/broker_client.py +61 -0
- package/src/devcouncil/codeintel/debug/consent.py +36 -0
- package/src/devcouncil/codeintel/debug/discovery.py +132 -0
- package/src/devcouncil/codeintel/debug/fingerprint.py +85 -0
- package/src/devcouncil/codeintel/debug/protocol.py +259 -0
- package/src/devcouncil/codeintel/debug/python_trace_runner.py +81 -0
- package/src/devcouncil/codeintel/debug/session.py +238 -0
- package/src/devcouncil/codeintel/debug/tracing.py +201 -0
- package/src/devcouncil/codeintel/languages/__init__.py +17 -0
- package/src/devcouncil/codeintel/languages/generic_extractor.py +236 -0
- package/src/devcouncil/codeintel/languages/registry.py +149 -0
- package/src/devcouncil/codeintel/languages/workers.py +245 -0
- package/src/devcouncil/codeintel/query/__init__.py +5 -0
- package/src/devcouncil/codeintel/query/engine.py +289 -0
- package/src/devcouncil/codeintel/resolution/__init__.py +6 -0
- package/src/devcouncil/codeintel/resolution/abstract_state.py +301 -0
- package/src/devcouncil/codeintel/resolution/frameworks/__init__.py +33 -0
- package/src/devcouncil/codeintel/resolution/frameworks/base.py +46 -0
- package/src/devcouncil/codeintel/resolution/frameworks/di.py +56 -0
- package/src/devcouncil/codeintel/resolution/frameworks/events.py +45 -0
- package/src/devcouncil/codeintel/resolution/frameworks/routes.py +88 -0
- package/src/devcouncil/codeintel/resolution/semantic.py +887 -0
- package/src/devcouncil/codeintel/service.py +104 -0
- package/src/devcouncil/codeintel/store/__init__.py +15 -0
- package/src/devcouncil/codeintel/store/sqlite.py +1565 -0
- package/src/devcouncil/codeintel/sync/__init__.py +19 -0
- package/src/devcouncil/codeintel/sync/coordinator.py +430 -0
- package/src/devcouncil/codeintel/sync/incremental.py +484 -0
- package/src/devcouncil/codeintel/sync/lease.py +96 -0
- package/src/devcouncil/codeintel/sync/scope.py +98 -0
- package/src/devcouncil/council/__init__.py +4 -0
- package/src/devcouncil/council/prompts/__init__.py +4 -0
- package/src/devcouncil/domain/checkpoint_refs.py +17 -0
- package/src/devcouncil/domain/evidence.py +1 -0
- package/src/devcouncil/domain/gap.py +10 -0
- package/src/devcouncil/domain/requirement.py +5 -1
- package/src/devcouncil/domain/task.py +43 -2
- package/src/devcouncil/execution/checkpoints.py +25 -31
- package/src/devcouncil/execution/context_builder.py +15 -44
- package/src/devcouncil/execution/fs_watcher.py +64 -0
- package/src/devcouncil/execution/gated_write.py +203 -0
- package/src/devcouncil/execution/handoff.py +2 -1
- package/src/devcouncil/execution/hook_policy.py +19 -5
- package/src/devcouncil/execution/lease_ops.py +177 -0
- package/src/devcouncil/execution/lease_validation.py +71 -0
- package/src/devcouncil/execution/patch.py +3 -0
- package/src/devcouncil/execution/permissions.py +1 -0
- package/src/devcouncil/execution/policy_engine.py +205 -10
- package/src/devcouncil/execution/prompt_builder.py +278 -33
- package/src/devcouncil/execution/run_trace.py +356 -0
- package/src/devcouncil/execution/shell_session.py +46 -5
- package/src/devcouncil/execution/stop_gate.py +746 -0
- package/src/devcouncil/execution/stop_gate_history.py +113 -0
- package/src/devcouncil/execution/stop_gate_state.py +54 -0
- package/src/devcouncil/execution/stop_gate_verify_cache.py +69 -0
- package/src/devcouncil/execution/task_gate_ops.py +590 -0
- package/src/devcouncil/execution/task_runner.py +19 -0
- package/src/devcouncil/executors/advisor_tool.py +315 -0
- package/src/devcouncil/executors/agent_registry.py +125 -17
- package/src/devcouncil/executors/claude_sdk.py +376 -0
- package/src/devcouncil/executors/coding_cli.py +724 -25
- package/src/devcouncil/executors/mini_swe.py +50 -8
- package/src/devcouncil/executors/native/agent.py +224 -19
- package/src/devcouncil/executors/openhands.py +50 -8
- package/src/devcouncil/executors/transient_retry.py +99 -0
- package/src/devcouncil/gating/checks/clean_git.py +5 -2
- package/src/devcouncil/gating/checks/planned_files_check.py +38 -11
- package/src/devcouncil/gating/checks/secret_scan_check.py +2 -2
- package/src/devcouncil/gating/policy.py +46 -2
- package/src/devcouncil/indexing/ast_matcher.py +41 -4
- package/src/devcouncil/indexing/graph/__init__.py +78 -0
- package/src/devcouncil/indexing/graph/api_routes.py +522 -0
- package/src/devcouncil/indexing/graph/build.py +862 -0
- package/src/devcouncil/indexing/graph/cache.py +329 -0
- package/src/devcouncil/indexing/graph/communities.py +28 -0
- package/src/devcouncil/indexing/graph/cypher.py +107 -0
- package/src/devcouncil/indexing/graph/embeddings.py +194 -0
- package/src/devcouncil/indexing/graph/export.py +381 -0
- package/src/devcouncil/indexing/graph/export_links.py +81 -0
- package/src/devcouncil/indexing/graph/extract_python.py +307 -0
- package/src/devcouncil/indexing/graph/extract_ts.py +1205 -0
- package/src/devcouncil/indexing/graph/intel.py +668 -0
- package/src/devcouncil/indexing/graph/liveness.py +992 -0
- package/src/devcouncil/indexing/graph/okf_export.py +65 -0
- package/src/devcouncil/indexing/graph/pdg/__init__.py +67 -0
- package/src/devcouncil/indexing/graph/pdg/build.py +11 -0
- package/src/devcouncil/indexing/graph/pdg/cdg.py +41 -0
- package/src/devcouncil/indexing/graph/pdg/cfg.py +199 -0
- package/src/devcouncil/indexing/graph/pdg/query.py +21 -0
- package/src/devcouncil/indexing/graph/pdg/reaching_def.py +126 -0
- package/src/devcouncil/indexing/graph/pdg/schema.py +253 -0
- package/src/devcouncil/indexing/graph/pdg/taint.py +154 -0
- package/src/devcouncil/indexing/graph/query.py +302 -0
- package/src/devcouncil/indexing/graph/resolve.py +1020 -0
- package/src/devcouncil/indexing/graph/schema.py +103 -0
- package/src/devcouncil/indexing/graph_index.py +20 -29
- package/src/devcouncil/indexing/lsp.py +57 -25
- package/src/devcouncil/indexing/lsp_client.py +577 -0
- package/src/devcouncil/indexing/map_artifacts.py +355 -0
- package/src/devcouncil/indexing/map_refresh.py +141 -0
- package/src/devcouncil/indexing/repo_mapper.py +1509 -138
- package/src/devcouncil/indexing/semantic_index.py +12 -6
- package/src/devcouncil/indexing/subsystem_map.py +163 -0
- package/src/devcouncil/indexing/ts_imports.py +343 -0
- package/src/devcouncil/indexing/viz.py +964 -0
- package/src/devcouncil/indexing/walk.py +52 -0
- package/src/devcouncil/indexing/wiring.py +1776 -0
- package/src/devcouncil/integrations/actions.py +27 -4
- package/src/devcouncil/integrations/check.py +211 -16
- package/src/devcouncil/integrations/claude_assets.py +209 -12
- package/src/devcouncil/integrations/clients/__init__.py +1 -0
- package/src/devcouncil/integrations/clients/aider.py +52 -0
- package/src/devcouncil/integrations/clients/antigravity.py +87 -0
- package/src/devcouncil/integrations/clients/claude.py +339 -0
- package/src/devcouncil/integrations/clients/codex.py +39 -0
- package/src/devcouncil/integrations/clients/common.py +332 -0
- package/src/devcouncil/integrations/clients/cursor.py +164 -0
- package/src/devcouncil/integrations/clients/gemini.py +49 -0
- package/src/devcouncil/integrations/clients/grok.py +105 -0
- package/src/devcouncil/integrations/clients/hooks.py +500 -0
- package/src/devcouncil/integrations/clients/opencode.py +96 -0
- package/src/devcouncil/integrations/clients/warp.py +75 -0
- package/src/devcouncil/integrations/code_review_graph.py +2 -2
- package/src/devcouncil/integrations/github.py +73 -7
- package/src/devcouncil/integrations/integration_cli.py +197 -0
- package/src/devcouncil/integrations/mcp/handlers/__init__.py +1 -0
- package/src/devcouncil/integrations/mcp/handlers/ast_lsp.py +77 -0
- package/src/devcouncil/integrations/mcp/handlers/checkout.py +50 -0
- package/src/devcouncil/integrations/mcp/handlers/cli_gate.py +43 -0
- package/src/devcouncil/integrations/mcp/handlers/codeintel.py +182 -0
- package/src/devcouncil/integrations/mcp/handlers/debug.py +236 -0
- package/src/devcouncil/integrations/mcp/handlers/evidence.py +70 -0
- package/src/devcouncil/integrations/mcp/handlers/git.py +281 -0
- package/src/devcouncil/integrations/mcp/handlers/graph.py +34 -0
- package/src/devcouncil/integrations/mcp/handlers/handoff.py +53 -0
- package/src/devcouncil/integrations/mcp/handlers/knowledge.py +28 -0
- package/src/devcouncil/integrations/mcp/handlers/lease.py +70 -0
- package/src/devcouncil/integrations/mcp/handlers/live.py +108 -0
- package/src/devcouncil/integrations/mcp/handlers/map.py +676 -0
- package/src/devcouncil/integrations/mcp/handlers/next_task.py +35 -0
- package/src/devcouncil/integrations/mcp/handlers/policy.py +80 -0
- package/src/devcouncil/integrations/mcp/handlers/prompts.py +168 -0
- package/src/devcouncil/integrations/mcp/handlers/provenance.py +87 -0
- package/src/devcouncil/integrations/mcp/handlers/read.py +103 -0
- package/src/devcouncil/integrations/mcp/handlers/router_cache.py +53 -0
- package/src/devcouncil/integrations/mcp/handlers/run.py +53 -0
- package/src/devcouncil/integrations/mcp/handlers/runs.py +69 -0
- package/src/devcouncil/integrations/mcp/handlers/scope.py +56 -0
- package/src/devcouncil/integrations/mcp/handlers/status.py +199 -0
- package/src/devcouncil/integrations/mcp/handlers/task.py +88 -0
- package/src/devcouncil/integrations/mcp/handlers/tool_specs.py +922 -0
- package/src/devcouncil/integrations/mcp/handlers/trace.py +65 -0
- package/src/devcouncil/integrations/mcp/handlers/verify.py +45 -0
- package/src/devcouncil/integrations/mcp/handlers/wiki.py +53 -0
- package/src/devcouncil/integrations/mcp/handlers/write.py +69 -0
- package/src/devcouncil/integrations/mcp/server.py +265 -2391
- package/src/devcouncil/integrations/mcp/util.py +325 -0
- package/src/devcouncil/integrations/setup.py +152 -0
- package/src/devcouncil/knowledge/fetch.py +4 -0
- package/src/devcouncil/knowledge/knowledge_select.py +38 -0
- package/src/devcouncil/knowledge/okf.py +2 -1
- package/src/devcouncil/knowledge/resource_discovery.py +40 -0
- package/src/devcouncil/knowledge/wiki.py +643 -0
- package/src/devcouncil/knowledge/wiki_read.py +87 -0
- package/src/devcouncil/live/cards.py +7 -7
- package/src/devcouncil/live/models.py +4 -1
- package/src/devcouncil/live/reviewer.py +90 -11
- package/src/devcouncil/live/signals.py +4 -2
- package/src/devcouncil/live/summary.py +21 -3
- package/src/devcouncil/live/tasks.py +12 -3
- package/src/devcouncil/live/transcripts.py +69 -2
- package/src/devcouncil/llm/cache.py +5 -6
- package/src/devcouncil/llm/model_defaults.yaml +10 -10
- package/src/devcouncil/llm/provider.py +647 -73
- package/src/devcouncil/llm/router.py +271 -46
- package/src/devcouncil/llm/semantic_bridge.py +614 -0
- package/src/devcouncil/optimization/gepa_agent.py +6 -4
- package/src/devcouncil/optimization/skillopt.py +9 -5
- package/src/devcouncil/planning/arbiter_service.py +12 -3
- package/src/devcouncil/planning/correction_manifest.py +107 -10
- package/src/devcouncil/planning/plan_difficulty.py +69 -0
- package/src/devcouncil/planning/plan_service.py +5 -2
- package/src/devcouncil/planning/planned_files_reconcile.py +191 -0
- package/src/devcouncil/planning/prompt_enhancer_service.py +6 -5
- package/src/devcouncil/planning/question_conversion.py +56 -0
- package/src/devcouncil/planning/spec_service.py +9 -3
- package/src/devcouncil/repo/ci_scaffold.py +197 -1
- package/src/devcouncil/repo/gitignore.py +1 -2
- package/src/devcouncil/reporting/evidence_export.py +124 -0
- package/src/devcouncil/reporting/evidence_html.py +210 -0
- package/src/devcouncil/reporting/json_report.py +16 -12
- package/src/devcouncil/reporting/markdown_report.py +38 -9
- package/src/devcouncil/reporting/mcp_resources.py +142 -0
- package/src/devcouncil/reporting/report_builder.py +40 -4
- package/src/devcouncil/reporting/task_provenance.py +42 -0
- package/src/devcouncil/reporting/verdict.py +75 -0
- package/src/devcouncil/skills/library/README.md +1 -0
- package/src/devcouncil/skills/library/devcouncil-hero-loop.md +109 -0
- package/src/devcouncil/skills/library/devcouncil-verification.md +109 -0
- package/src/devcouncil/skills/library/devcouncil.md +93 -0
- package/src/devcouncil/skills/registry.py +43 -12
- package/src/devcouncil/storage/db.py +57 -11
- package/src/devcouncil/storage/models.py +6 -0
- package/src/devcouncil/storage/native.py +5 -3
- package/src/devcouncil/storage/repositories.py +50 -18
- package/src/devcouncil/telemetry/context.py +28 -0
- package/src/devcouncil/telemetry/cost.py +4 -5
- package/src/devcouncil/telemetry/logging_setup.py +78 -11
- package/src/devcouncil/telemetry/model_pricing.yaml +7 -0
- package/src/devcouncil/telemetry/stages.py +27 -2
- package/src/devcouncil/telemetry/tracker.py +50 -13
- package/src/devcouncil/ui/dashboard.py +120 -8
- package/src/devcouncil/utils/fsio.py +58 -0
- package/src/devcouncil/utils/git_snapshot.py +112 -0
- package/src/devcouncil/utils/json_persist.py +53 -0
- package/src/devcouncil/utils/proc.py +89 -0
- package/src/devcouncil/verification/acceptance_compiler.py +36 -13
- package/src/devcouncil/verification/ad_hoc_check.py +95 -3
- package/src/devcouncil/verification/checks/__init__.py +41 -0
- package/src/devcouncil/verification/checks/acceptance.py +39 -0
- package/src/devcouncil/verification/checks/acceptance_corpus.py +194 -0
- package/src/devcouncil/verification/checks/acceptance_evidence.py +239 -0
- package/src/devcouncil/verification/checks/command_evidence.py +148 -0
- package/src/devcouncil/verification/checks/compiled_acceptance.py +179 -0
- package/src/devcouncil/verification/checks/corpus_stale.py +124 -0
- package/src/devcouncil/verification/checks/corpus_verification.py +9 -0
- package/src/devcouncil/verification/checks/dead_symbols.py +360 -0
- package/src/devcouncil/verification/checks/diff_coverage_gate.py +101 -0
- package/src/devcouncil/verification/checks/doc_code_ref.py +79 -0
- package/src/devcouncil/verification/checks/liveness_ratchet.py +336 -0
- package/src/devcouncil/verification/checks/orphan_diff.py +104 -0
- package/src/devcouncil/verification/checks/planned_files.py +98 -0
- package/src/devcouncil/verification/checks/semantic_diff.py +241 -0
- package/src/devcouncil/verification/checks/stale_map.py +80 -0
- package/src/devcouncil/verification/checks/stub_scan.py +71 -0
- package/src/devcouncil/verification/checks/subsystem_boundary.py +103 -0
- package/src/devcouncil/verification/checks/wiring.py +216 -0
- package/src/devcouncil/verification/claims/__init__.py +23 -0
- package/src/devcouncil/verification/claims/checks.py +395 -0
- package/src/devcouncil/verification/claims/mapper.py +168 -0
- package/src/devcouncil/verification/claims/models.py +39 -0
- package/src/devcouncil/verification/claims/transcript.py +92 -0
- package/src/devcouncil/verification/claims/verdict.py +88 -0
- package/src/devcouncil/verification/command_evidence.py +170 -0
- package/src/devcouncil/verification/command_malformation.py +147 -0
- package/src/devcouncil/verification/command_runner.py +164 -0
- package/src/devcouncil/verification/coverage_measurement.py +292 -0
- package/src/devcouncil/verification/diff_coverage.py +151 -0
- package/src/devcouncil/verification/difficulty.py +296 -0
- package/src/devcouncil/verification/effort_heuristics.py +178 -0
- package/src/devcouncil/verification/gap_ids.py +63 -0
- package/src/devcouncil/verification/gate_cache.py +194 -0
- package/src/devcouncil/verification/gate_selector.py +344 -0
- package/src/devcouncil/verification/git_diff_fallback.py +272 -0
- package/src/devcouncil/verification/implementation_reviewer.py +13 -0
- package/src/devcouncil/verification/incremental_check.py +241 -0
- package/src/devcouncil/verification/next_actions.py +60 -1
- package/src/devcouncil/verification/rigor_analytics.py +130 -0
- package/src/devcouncil/verification/sandbox.py +38 -11
- package/src/devcouncil/verification/stub_detector.py +369 -0
- package/src/devcouncil/verification/test_resolver.py +67 -1
- package/src/devcouncil/verification/verifier.py +137 -1666
- package/src/devcouncil/verification/verify_orchestration.py +610 -0
- package/src/devcouncil/verification/verify_setup.py +176 -0
- package/src/devcouncil/verification/wiki_refresh.py +208 -0
- package/src/semantic_layer/__init__.py +58 -0
- package/src/semantic_layer/benchmark.py +75 -0
- package/src/semantic_layer/cache.py +290 -0
- package/src/semantic_layer/compressor.py +137 -0
- package/src/semantic_layer/config.py +75 -0
- package/src/semantic_layer/embeddings.py +69 -0
- package/src/semantic_layer/llm_backends.py +99 -0
- package/src/semantic_layer/pipeline.py +111 -0
- package/src/semantic_layer/router.py +128 -0
- package/src/semantic_layer/tuner.py +72 -0
- package/uv.lock +973 -9
- package/src/devcouncil/artifacts/migrations.py +0 -20
- package/src/devcouncil/artifacts/schemas.py +0 -23
- package/src/devcouncil/artifacts/serializer.py +0 -21
- package/src/devcouncil/integrations/gitnexus.py +0 -70
- package/src/devcouncil/integrations/graphify.py +0 -34
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
"""Verify-task setup and teardown helpers extracted from Verifier."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import logging
|
|
7
|
+
from dataclasses import dataclass, field
|
|
8
|
+
from typing import Any, List, Optional, TYPE_CHECKING
|
|
9
|
+
|
|
10
|
+
from devcouncil.app.config import load_config
|
|
11
|
+
from devcouncil.domain.requirement import Requirement
|
|
12
|
+
from devcouncil.domain.task import Task
|
|
13
|
+
from devcouncil.telemetry.stages import log_step
|
|
14
|
+
from devcouncil.utils.git_snapshot import GitWorktreeSnapshot
|
|
15
|
+
from devcouncil.verification.difficulty import resolve_rigor_policy
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from devcouncil.verification.verifier import Verifier
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
_AD_HOC_TASK_ID = "CHECK"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass
|
|
26
|
+
class VerifyRunContext:
|
|
27
|
+
"""Configuration and diff state primed at the start of verify_task."""
|
|
28
|
+
|
|
29
|
+
config: Any | None
|
|
30
|
+
rigor: Any
|
|
31
|
+
ac_samples: int = 1
|
|
32
|
+
ac_repair_attempts: int = 1
|
|
33
|
+
ac_per_criterion: bool = False
|
|
34
|
+
command_timeout: int = 300
|
|
35
|
+
changed_files: List[str] = field(default_factory=list)
|
|
36
|
+
diff_content: str = ""
|
|
37
|
+
diff_empty: bool = True
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def prime_verify_memos(verifier: Verifier) -> None:
|
|
41
|
+
"""Batch git plumbing and prime per-call caches."""
|
|
42
|
+
snapshot = GitWorktreeSnapshot.capture(verifier.project_root)
|
|
43
|
+
verifier._git_fallback.git_snapshot = snapshot
|
|
44
|
+
verifier._git_fallback.untracked_cache = snapshot.untracked_files
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def resolve_verify_context(
|
|
48
|
+
verifier: Verifier,
|
|
49
|
+
task: Task,
|
|
50
|
+
requirements: List[Requirement],
|
|
51
|
+
) -> VerifyRunContext:
|
|
52
|
+
"""Load config, rigor policy, and diff snapshot for a verify run."""
|
|
53
|
+
ac_samples, ac_repair_attempts = 1, 1
|
|
54
|
+
ac_per_criterion = False
|
|
55
|
+
cfg = None
|
|
56
|
+
command_timeout = 300
|
|
57
|
+
try:
|
|
58
|
+
from devcouncil.app.config import role_runs_on_local_provider
|
|
59
|
+
|
|
60
|
+
cfg = load_config(verifier.project_root)
|
|
61
|
+
command_timeout = cfg.execution.command_timeout
|
|
62
|
+
local_monitor = role_runs_on_local_provider(cfg, "implementation_reviewer")
|
|
63
|
+
ac_samples, ac_repair_attempts, ac_per_criterion = (
|
|
64
|
+
cfg.verification.acceptance_checks.resolved(local_monitor)
|
|
65
|
+
)
|
|
66
|
+
# Explicit overrides are honored but never silently: single-shot acceptance
|
|
67
|
+
# checks on a local monitor rubber-stamped real defects in calibration probes.
|
|
68
|
+
# Once per process — verify runs per task, and 20 identical lines are spam.
|
|
69
|
+
from devcouncil.telemetry.logging_setup import warn_once
|
|
70
|
+
|
|
71
|
+
for warning in cfg.verification.acceptance_checks.unsafe_override_warnings(local_monitor):
|
|
72
|
+
warn_once(logger, warning)
|
|
73
|
+
except Exception as exc:
|
|
74
|
+
# Without config we cannot detect a local monitor, so this falls back to
|
|
75
|
+
# single-shot cloud defaults — the exact mode that is unsafe if the monitor
|
|
76
|
+
# IS local. Say so instead of degrading silently.
|
|
77
|
+
from devcouncil.telemetry.logging_setup import warn_once
|
|
78
|
+
|
|
79
|
+
warn_once(
|
|
80
|
+
logger,
|
|
81
|
+
f"Verification config unavailable ({exc}); acceptance checks fall back to "
|
|
82
|
+
"single-shot defaults (samples=1). If the monitor runs on a local model, "
|
|
83
|
+
"fix the config load — single-shot local checks miss real defects.",
|
|
84
|
+
)
|
|
85
|
+
cfg = None
|
|
86
|
+
rigor = resolve_rigor_policy(task, requirements, config=cfg)
|
|
87
|
+
if rigor.min_acceptance_samples > 1:
|
|
88
|
+
ac_samples = max(ac_samples, rigor.min_acceptance_samples)
|
|
89
|
+
log_step(
|
|
90
|
+
"verify/1: resolved rigor policy",
|
|
91
|
+
project_root=verifier.project_root,
|
|
92
|
+
task_id=task.id,
|
|
93
|
+
difficulty=rigor.difficulty,
|
|
94
|
+
rigor_applied=list(rigor.applied),
|
|
95
|
+
)
|
|
96
|
+
verifier._command_timeout_cache = command_timeout
|
|
97
|
+
is_ad_hoc_check = task.id == _AD_HOC_TASK_ID
|
|
98
|
+
# CHECK represents the live working tree, including edits recorded in the global
|
|
99
|
+
# baseline. Baseline/checkpoint subtraction only applies to persisted workflow tasks.
|
|
100
|
+
changed_files = (
|
|
101
|
+
verifier.get_changed_files()
|
|
102
|
+
if is_ad_hoc_check
|
|
103
|
+
else verifier.get_task_changed_files(task.id)
|
|
104
|
+
)
|
|
105
|
+
diff_content = verifier.get_diff()
|
|
106
|
+
if not is_ad_hoc_check:
|
|
107
|
+
committed_diff = verifier._committed_task_diff(task.id)
|
|
108
|
+
if committed_diff.strip():
|
|
109
|
+
diff_content = committed_diff
|
|
110
|
+
diff_empty = not bool(diff_content.strip())
|
|
111
|
+
log_step(
|
|
112
|
+
"verify/2: collected diff and changed files",
|
|
113
|
+
project_root=verifier.project_root,
|
|
114
|
+
task_id=task.id,
|
|
115
|
+
changed_files=len(changed_files),
|
|
116
|
+
diff_empty=diff_empty,
|
|
117
|
+
)
|
|
118
|
+
return VerifyRunContext(
|
|
119
|
+
config=cfg,
|
|
120
|
+
rigor=rigor,
|
|
121
|
+
ac_samples=ac_samples,
|
|
122
|
+
ac_repair_attempts=ac_repair_attempts,
|
|
123
|
+
ac_per_criterion=ac_per_criterion,
|
|
124
|
+
command_timeout=command_timeout,
|
|
125
|
+
changed_files=changed_files,
|
|
126
|
+
diff_content=diff_content,
|
|
127
|
+
diff_empty=diff_empty,
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def start_verify_futures(
|
|
132
|
+
verifier: Verifier,
|
|
133
|
+
*,
|
|
134
|
+
task: Task,
|
|
135
|
+
requirements: List[Requirement],
|
|
136
|
+
diff_content: str,
|
|
137
|
+
ac_samples: int,
|
|
138
|
+
ac_per_criterion: bool,
|
|
139
|
+
) -> tuple[Optional[asyncio.Task[Any]], Optional[asyncio.Task[Any]]]:
|
|
140
|
+
"""Launch acceptance-compiler and implementation-reviewer LLM passes."""
|
|
141
|
+
compile_future: Optional[asyncio.Task[Any]] = None
|
|
142
|
+
if verifier.acceptance_compiler and diff_content and task.acceptance_criterion_ids:
|
|
143
|
+
if hasattr(verifier.acceptance_compiler, "compile_candidates"):
|
|
144
|
+
compile_coro = verifier.acceptance_compiler.compile_candidates(
|
|
145
|
+
task, requirements, diff_content, samples=ac_samples,
|
|
146
|
+
per_criterion=ac_per_criterion,
|
|
147
|
+
)
|
|
148
|
+
else:
|
|
149
|
+
compile_coro = verifier.acceptance_compiler.compile(task, requirements, diff_content)
|
|
150
|
+
compile_future = asyncio.create_task(compile_coro)
|
|
151
|
+
review_future: Optional[asyncio.Task[Any]] = None
|
|
152
|
+
if verifier.reviewer and diff_content:
|
|
153
|
+
review_future = asyncio.create_task(
|
|
154
|
+
verifier.reviewer.review_changes(task, requirements, diff_content)
|
|
155
|
+
)
|
|
156
|
+
return compile_future, review_future
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
async def cleanup_verify_futures(
|
|
160
|
+
verifier: Verifier,
|
|
161
|
+
compile_future: Optional[asyncio.Task[Any]],
|
|
162
|
+
review_future: Optional[asyncio.Task[Any]],
|
|
163
|
+
) -> None:
|
|
164
|
+
"""Drain background LLM tasks and clear per-call memos."""
|
|
165
|
+
for fut in (compile_future, review_future):
|
|
166
|
+
if fut is not None:
|
|
167
|
+
if not fut.done():
|
|
168
|
+
fut.cancel()
|
|
169
|
+
try:
|
|
170
|
+
await fut
|
|
171
|
+
except (asyncio.CancelledError, Exception):
|
|
172
|
+
pass
|
|
173
|
+
verifier._git_fallback.git_snapshot = None
|
|
174
|
+
verifier._git_fallback.untracked_cache = None
|
|
175
|
+
verifier._command_timeout_cache = None
|
|
176
|
+
verifier._project_deps_cache = None
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
"""Post-verify wiki-freshness trigger for large refactors.
|
|
2
|
+
|
|
3
|
+
The agent-facing codebase wiki (``dev wiki``) drifts as the code changes. A big
|
|
4
|
+
refactor — one that spans several subsystems or touches many files — is exactly the kind
|
|
5
|
+
of change that leaves the wiki stale. This module decides, after a verify run, whether a
|
|
6
|
+
change was "large" and either FLAGS the pages that a refresh would rewrite (cheap, the
|
|
7
|
+
default) or actually triggers ``dev wiki update --no-llm`` as a post-step
|
|
8
|
+
(``verification.wiki_refresh.auto_update``).
|
|
9
|
+
|
|
10
|
+
Deliberately best-effort and non-blocking: it is an orientation aid, never a gate. Any
|
|
11
|
+
failure (no map, no wiki, config error) degrades to "not considered" and is logged, not
|
|
12
|
+
raised. Flagging is free of model calls; auto-update runs the deterministic skeleton.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import logging
|
|
18
|
+
from dataclasses import dataclass, field
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
from typing import TYPE_CHECKING, List, Mapping, Optional, Sequence
|
|
21
|
+
|
|
22
|
+
from devcouncil.indexing.subsystem_map import areas_touched
|
|
23
|
+
|
|
24
|
+
if TYPE_CHECKING:
|
|
25
|
+
from devcouncil.domain.gap import Gap
|
|
26
|
+
|
|
27
|
+
logger = logging.getLogger(__name__)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass
|
|
31
|
+
class WikiRefreshOutcome:
|
|
32
|
+
considered: bool = False # was the change large enough to act on?
|
|
33
|
+
triggered: bool = False # did we actually run an update?
|
|
34
|
+
subsystems_touched: int = 0
|
|
35
|
+
files_touched: int = 0
|
|
36
|
+
stale_pages: List[str] = field(default_factory=list)
|
|
37
|
+
reason: str = ""
|
|
38
|
+
|
|
39
|
+
def to_dict(self) -> dict:
|
|
40
|
+
return {
|
|
41
|
+
"considered": self.considered,
|
|
42
|
+
"triggered": self.triggered,
|
|
43
|
+
"subsystems_touched": self.subsystems_touched,
|
|
44
|
+
"files_touched": self.files_touched,
|
|
45
|
+
"stale_pages": list(self.stale_pages),
|
|
46
|
+
"reason": self.reason,
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _resolve_config(project_root: Path, config):
|
|
51
|
+
if config is not None:
|
|
52
|
+
return config
|
|
53
|
+
try:
|
|
54
|
+
from devcouncil.app.config import load_config
|
|
55
|
+
|
|
56
|
+
return load_config(project_root).verification.wiki_refresh
|
|
57
|
+
except Exception as exc:
|
|
58
|
+
logger.debug("wiki refresh: config unavailable: %s", exc)
|
|
59
|
+
return None
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def is_large_change(
|
|
63
|
+
*, files_touched: int, subsystems_touched: int, min_files: int, min_subsystems: int
|
|
64
|
+
) -> bool:
|
|
65
|
+
"""A change is "large" when it spans many subsystems OR touches many files."""
|
|
66
|
+
return subsystems_touched >= min_subsystems or files_touched >= min_files
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _stale_pages(project_root: Path) -> List[str]:
|
|
70
|
+
"""Pages a refresh would rewrite, or ``[]`` when the wiki/map is unavailable."""
|
|
71
|
+
try:
|
|
72
|
+
from devcouncil.indexing.repo_mapper import RepoMap
|
|
73
|
+
from devcouncil.knowledge.wiki import wiki_dir_for, wiki_stale_pages
|
|
74
|
+
|
|
75
|
+
map_path = project_root / ".devcouncil" / "repo_map.json"
|
|
76
|
+
wiki_dir = wiki_dir_for(project_root)
|
|
77
|
+
if not map_path.is_file() or not (wiki_dir / "index.md").is_file():
|
|
78
|
+
return []
|
|
79
|
+
repo_map = RepoMap.model_validate_json(map_path.read_text(encoding="utf-8"))
|
|
80
|
+
return sorted(wiki_stale_pages(project_root, repo_map, wiki_dir).keys())
|
|
81
|
+
except Exception as exc:
|
|
82
|
+
logger.debug("wiki refresh: stale-page detection failed: %s", exc)
|
|
83
|
+
return []
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _run_update(project_root: Path) -> bool:
|
|
87
|
+
"""Run the deterministic wiki refresh (no model calls). True on success."""
|
|
88
|
+
try:
|
|
89
|
+
from devcouncil.knowledge.wiki import refresh_wiki
|
|
90
|
+
|
|
91
|
+
refresh_wiki(project_root, llm=False, force=False, remap=False)
|
|
92
|
+
return True
|
|
93
|
+
except Exception as exc:
|
|
94
|
+
logger.warning("wiki refresh: auto-update failed: %s", exc)
|
|
95
|
+
return False
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def wiki_refresh_advisory_gap(
|
|
99
|
+
outcome: WikiRefreshOutcome,
|
|
100
|
+
*,
|
|
101
|
+
task_id: str,
|
|
102
|
+
gap_id: str,
|
|
103
|
+
) -> "Gap | None":
|
|
104
|
+
"""Return a non-blocking advisory gap when stale wiki pages were flagged."""
|
|
105
|
+
if not (outcome.considered and not outcome.triggered and outcome.stale_pages):
|
|
106
|
+
return None
|
|
107
|
+
from devcouncil.domain.gap import Gap
|
|
108
|
+
|
|
109
|
+
preview = ", ".join(outcome.stale_pages[:5])
|
|
110
|
+
if len(outcome.stale_pages) > 5:
|
|
111
|
+
preview = f"{preview} (+{len(outcome.stale_pages) - 5} more)"
|
|
112
|
+
return Gap(
|
|
113
|
+
id=gap_id,
|
|
114
|
+
severity="low",
|
|
115
|
+
gap_type="architecture_drift",
|
|
116
|
+
task_id=task_id,
|
|
117
|
+
description=(
|
|
118
|
+
f"Codebase wiki may be stale after this large change: "
|
|
119
|
+
f"{len(outcome.stale_pages)} page(s) need refresh ({preview})."
|
|
120
|
+
),
|
|
121
|
+
evidence=list(outcome.stale_pages[:10]),
|
|
122
|
+
recommended_fix="Run `dev wiki update` to refresh agent-facing documentation.",
|
|
123
|
+
blocking=False,
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def evaluate_wiki_refresh(
|
|
128
|
+
project_root: Path,
|
|
129
|
+
changed_files: Sequence[str],
|
|
130
|
+
*,
|
|
131
|
+
repo_map: Optional[Mapping] = None,
|
|
132
|
+
config=None,
|
|
133
|
+
) -> WikiRefreshOutcome:
|
|
134
|
+
"""Decide whether the change warrants a wiki refresh and act per config.
|
|
135
|
+
|
|
136
|
+
Returns a :class:`WikiRefreshOutcome`. When the change is large: with
|
|
137
|
+
``auto_update`` it runs ``dev wiki update --no-llm`` and reports ``triggered``;
|
|
138
|
+
otherwise it reports the ``stale_pages`` a manual ``dev wiki update`` would rewrite.
|
|
139
|
+
"""
|
|
140
|
+
root = Path(project_root)
|
|
141
|
+
changed = [p for p in changed_files if p and p.strip()]
|
|
142
|
+
cfg = _resolve_config(root, config)
|
|
143
|
+
if cfg is not None and not getattr(cfg, "enabled", True):
|
|
144
|
+
return WikiRefreshOutcome(reason="wiki refresh disabled", files_touched=len(changed))
|
|
145
|
+
|
|
146
|
+
min_files = int(getattr(cfg, "min_files", 8)) if cfg is not None else 8
|
|
147
|
+
min_subsystems = int(getattr(cfg, "min_subsystems", 3)) if cfg is not None else 3
|
|
148
|
+
auto_update = bool(getattr(cfg, "auto_update", False)) if cfg is not None else False
|
|
149
|
+
|
|
150
|
+
if repo_map is None:
|
|
151
|
+
try:
|
|
152
|
+
from devcouncil.utils.json_persist import read_json
|
|
153
|
+
|
|
154
|
+
map_path = root / ".devcouncil" / "repo_map.json"
|
|
155
|
+
repo_map = read_json(map_path) if map_path.is_file() else None
|
|
156
|
+
except Exception:
|
|
157
|
+
repo_map = None
|
|
158
|
+
|
|
159
|
+
areas = areas_touched(changed, repo_map) if repo_map else []
|
|
160
|
+
files_touched = len(changed)
|
|
161
|
+
subsystems_touched = len(areas)
|
|
162
|
+
|
|
163
|
+
if not is_large_change(
|
|
164
|
+
files_touched=files_touched,
|
|
165
|
+
subsystems_touched=subsystems_touched,
|
|
166
|
+
min_files=min_files,
|
|
167
|
+
min_subsystems=min_subsystems,
|
|
168
|
+
):
|
|
169
|
+
return WikiRefreshOutcome(
|
|
170
|
+
considered=False,
|
|
171
|
+
files_touched=files_touched,
|
|
172
|
+
subsystems_touched=subsystems_touched,
|
|
173
|
+
reason=(
|
|
174
|
+
f"change below threshold ({files_touched} file(s), "
|
|
175
|
+
f"{subsystems_touched} subsystem(s); need >={min_files} files "
|
|
176
|
+
f"or >={min_subsystems} subsystems)"
|
|
177
|
+
),
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
outcome = WikiRefreshOutcome(
|
|
181
|
+
considered=True,
|
|
182
|
+
files_touched=files_touched,
|
|
183
|
+
subsystems_touched=subsystems_touched,
|
|
184
|
+
)
|
|
185
|
+
if auto_update:
|
|
186
|
+
outcome.triggered = _run_update(root)
|
|
187
|
+
outcome.reason = (
|
|
188
|
+
"large change — ran `dev wiki update --no-llm`"
|
|
189
|
+
if outcome.triggered else "large change — auto-update attempted but failed"
|
|
190
|
+
)
|
|
191
|
+
logger.info(
|
|
192
|
+
"wiki refresh: large change (%d files / %d subsystems) — auto-update %s",
|
|
193
|
+
files_touched, subsystems_touched, "ok" if outcome.triggered else "failed",
|
|
194
|
+
)
|
|
195
|
+
else:
|
|
196
|
+
outcome.stale_pages = _stale_pages(root)
|
|
197
|
+
outcome.reason = (
|
|
198
|
+
f"large change — {len(outcome.stale_pages)} stale wiki page(s); "
|
|
199
|
+
"run `dev wiki update`"
|
|
200
|
+
if outcome.stale_pages
|
|
201
|
+
else "large change — wiki may be stale; run `dev wiki update`"
|
|
202
|
+
)
|
|
203
|
+
logger.info(
|
|
204
|
+
"wiki refresh: large change (%d files / %d subsystems) — %d stale page(s) "
|
|
205
|
+
"flagged (run `dev wiki update`)",
|
|
206
|
+
files_touched, subsystems_touched, len(outcome.stale_pages),
|
|
207
|
+
)
|
|
208
|
+
return outcome
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""Semantic layer for local LLM pipelines — cache, routing, and RAG compression."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from .benchmark import benchmark_cache_path, run_benchmark
|
|
6
|
+
from .cache import CacheEntry, CacheLookupResult, SemanticCache
|
|
7
|
+
from .compressor import CompressedContext, SemanticCompressor
|
|
8
|
+
from .config import (
|
|
9
|
+
CacheConfig,
|
|
10
|
+
CompressorConfig,
|
|
11
|
+
EmbeddingConfig,
|
|
12
|
+
LLMConfig,
|
|
13
|
+
ModelTier,
|
|
14
|
+
RouterConfig,
|
|
15
|
+
SemanticLayerConfig,
|
|
16
|
+
)
|
|
17
|
+
from .embeddings import EmbeddingService
|
|
18
|
+
from .llm_backends import (
|
|
19
|
+
HuggingFaceBackend,
|
|
20
|
+
LLMBackend,
|
|
21
|
+
LlamaCppBackend,
|
|
22
|
+
OllamaBackend,
|
|
23
|
+
create_backend,
|
|
24
|
+
)
|
|
25
|
+
from .pipeline import PipelineResponse, SemanticPipeline
|
|
26
|
+
from .router import RouteDecision, SemanticRouter
|
|
27
|
+
from .tuner import LookupEvent, ThresholdAutoTuner
|
|
28
|
+
|
|
29
|
+
__all__ = [
|
|
30
|
+
"CacheConfig",
|
|
31
|
+
"CacheEntry",
|
|
32
|
+
"CacheLookupResult",
|
|
33
|
+
"CompressedContext",
|
|
34
|
+
"CompressorConfig",
|
|
35
|
+
"EmbeddingConfig",
|
|
36
|
+
"EmbeddingService",
|
|
37
|
+
"HuggingFaceBackend",
|
|
38
|
+
"LLMBackend",
|
|
39
|
+
"LLMConfig",
|
|
40
|
+
"LlamaCppBackend",
|
|
41
|
+
"LookupEvent",
|
|
42
|
+
"ModelTier",
|
|
43
|
+
"OllamaBackend",
|
|
44
|
+
"PipelineResponse",
|
|
45
|
+
"RouteDecision",
|
|
46
|
+
"RouterConfig",
|
|
47
|
+
"SemanticCache",
|
|
48
|
+
"SemanticCompressor",
|
|
49
|
+
"SemanticLayerConfig",
|
|
50
|
+
"SemanticPipeline",
|
|
51
|
+
"SemanticRouter",
|
|
52
|
+
"ThresholdAutoTuner",
|
|
53
|
+
"benchmark_cache_path",
|
|
54
|
+
"create_backend",
|
|
55
|
+
"run_benchmark",
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
__version__ = "1.0.0"
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""Benchmark harness for semantic layer latency and cache hit rate."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import statistics
|
|
6
|
+
import time
|
|
7
|
+
from typing import Sequence
|
|
8
|
+
|
|
9
|
+
from .config import SemanticLayerConfig
|
|
10
|
+
from .pipeline import SemanticPipeline
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def benchmark_cache_path(
|
|
14
|
+
pipeline: SemanticPipeline,
|
|
15
|
+
queries: Sequence[str],
|
|
16
|
+
warmup: int = 5,
|
|
17
|
+
) -> dict[str, float]:
|
|
18
|
+
"""Measure semantic-layer-only latency (no LLM) for cache hits."""
|
|
19
|
+
from .embeddings import EmbeddingService
|
|
20
|
+
|
|
21
|
+
embedder = EmbeddingService.get_instance()
|
|
22
|
+
latencies: list[float] = []
|
|
23
|
+
|
|
24
|
+
for q in list(queries)[:warmup]:
|
|
25
|
+
embedder.embed_one(q)
|
|
26
|
+
|
|
27
|
+
for q in queries:
|
|
28
|
+
t0 = time.perf_counter()
|
|
29
|
+
vec = embedder.embed_one(q)
|
|
30
|
+
pipeline.cache.lookup(q, vec)
|
|
31
|
+
latencies.append((time.perf_counter() - t0) * 1000)
|
|
32
|
+
|
|
33
|
+
if not latencies:
|
|
34
|
+
return {
|
|
35
|
+
"p50_ms": 0.0,
|
|
36
|
+
"p95_ms": 0.0,
|
|
37
|
+
"p99_ms": 0.0,
|
|
38
|
+
"mean_ms": 0.0,
|
|
39
|
+
"hit_rate": pipeline.cache.hit_rate,
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
latencies.sort()
|
|
43
|
+
last = len(latencies) - 1
|
|
44
|
+
return {
|
|
45
|
+
"p50_ms": latencies[len(latencies) // 2],
|
|
46
|
+
"p95_ms": latencies[int(last * 0.95)],
|
|
47
|
+
"p99_ms": latencies[int(last * 0.99)],
|
|
48
|
+
"mean_ms": statistics.mean(latencies),
|
|
49
|
+
"hit_rate": pipeline.cache.hit_rate,
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def run_benchmark(queries: list[str], documents: list[str] | None = None) -> None:
|
|
54
|
+
config = SemanticLayerConfig()
|
|
55
|
+
pipeline = SemanticPipeline(config)
|
|
56
|
+
|
|
57
|
+
# Prime cache
|
|
58
|
+
for q in queries[:10]:
|
|
59
|
+
pipeline.run(q, documents)
|
|
60
|
+
|
|
61
|
+
stats = benchmark_cache_path(pipeline, queries)
|
|
62
|
+
print("Semantic layer cache-path benchmark:")
|
|
63
|
+
for k, v in stats.items():
|
|
64
|
+
print(f" {k}: {v:.2f}" if isinstance(v, float) else f" {k}: {v}")
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
if __name__ == "__main__":
|
|
68
|
+
sample_queries = [
|
|
69
|
+
"What is Python?",
|
|
70
|
+
"Explain Python programming language",
|
|
71
|
+
"Define Python language",
|
|
72
|
+
"How do I write a for loop in Python?",
|
|
73
|
+
"Implement a distributed cache with consistent hashing",
|
|
74
|
+
] * 20
|
|
75
|
+
run_benchmark(sample_queries)
|