ifixai 3.0.2__py3-none-any.whl
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.
- ifixai/__init__.py +3 -0
- ifixai/_version.py +29 -0
- ifixai/api.py +310 -0
- ifixai/cli/__init__.py +0 -0
- ifixai/cli/_branding.py +229 -0
- ifixai/cli/_imecore_prompt.py +60 -0
- ifixai/cli/colors.py +160 -0
- ifixai/cli/compare.py +142 -0
- ifixai/cli/init.py +82 -0
- ifixai/cli/list_cmd.py +84 -0
- ifixai/cli/main.py +52 -0
- ifixai/cli/orchestrator.py +680 -0
- ifixai/cli/reports.py +41 -0
- ifixai/cli/run.py +1360 -0
- ifixai/cli/schemas.py +13 -0
- ifixai/cli/validate.py +34 -0
- ifixai/core/__init__.py +0 -0
- ifixai/core/concurrency.py +127 -0
- ifixai/core/connection.py +81 -0
- ifixai/core/context.py +142 -0
- ifixai/core/discovery.py +258 -0
- ifixai/core/fixture_loader.py +424 -0
- ifixai/core/governance_synthesis.py +131 -0
- ifixai/core/grounding.py +80 -0
- ifixai/core/refusal.py +39 -0
- ifixai/core/runner.py +660 -0
- ifixai/core/types.py +1366 -0
- ifixai/evaluation/__init__.py +0 -0
- ifixai/evaluation/analytic_judge.py +991 -0
- ifixai/evaluation/atomic_claims.py +641 -0
- ifixai/evaluation/checkpoint.py +144 -0
- ifixai/evaluation/embedding_classifier.py +141 -0
- ifixai/evaluation/errors.py +24 -0
- ifixai/evaluation/manifest.py +309 -0
- ifixai/evaluation/normalizer.py +64 -0
- ifixai/evaluation/pipeline.py +328 -0
- ifixai/evaluation/proportion_ci.py +126 -0
- ifixai/evaluation/response_classifier.py +152 -0
- ifixai/evaluation/schemas.py +13 -0
- ifixai/evaluation/types.py +11 -0
- ifixai/examples/__init__.py +0 -0
- ifixai/examples/ci_check.py +82 -0
- ifixai/examples/custom_fixture.py +43 -0
- ifixai/examples/run_all.py +24 -0
- ifixai/examples/run_single.py +19 -0
- ifixai/fixtures/default/fixture.yaml +303 -0
- ifixai/fixtures/examples/acme_legal.yaml +371 -0
- ifixai/fixtures/examples/customer_support.yaml +295 -0
- ifixai/fixtures/examples/healthcare.yaml +300 -0
- ifixai/fixtures/examples/helio_finance.yaml +617 -0
- ifixai/fixtures/examples/hermes_strict.yaml +761 -0
- ifixai/fixtures/examples/openclaw_consolidated.yaml +546 -0
- ifixai/fixtures/examples/openclaw_moderate.yaml +489 -0
- ifixai/fixtures/examples/openclaw_strict.yaml +454 -0
- ifixai/fixtures/examples/openwebui.yaml +366 -0
- ifixai/fixtures/examples/software_engineering.yaml +299 -0
- ifixai/fixtures/governance/mock.yaml +236 -0
- ifixai/fixtures/schema.json +192 -0
- ifixai/fixtures/smoke_tiny.yaml +104 -0
- ifixai/harness/__init__.py +0 -0
- ifixai/harness/adversarial_mutator.py +268 -0
- ifixai/harness/adversarial_rotator.py +57 -0
- ifixai/harness/base.py +449 -0
- ifixai/harness/consistency.py +121 -0
- ifixai/harness/injection_corpus.py +115 -0
- ifixai/harness/prompt_pool.py +172 -0
- ifixai/harness/registry.py +418 -0
- ifixai/harness/seed_resolver.py +7 -0
- ifixai/harness/synonyms.yaml +28 -0
- ifixai/harness/validator.py +160 -0
- ifixai/harness/version_enforcement.py +87 -0
- ifixai/inspections/__init__.py +0 -0
- ifixai/inspections/b01_tool_governance/definition.yaml +22 -0
- ifixai/inspections/b01_tool_governance/runner.py +531 -0
- ifixai/inspections/b02_non_llm_layer/definition.yaml +17 -0
- ifixai/inspections/b02_non_llm_layer/runner.py +185 -0
- ifixai/inspections/b03_auditability/definition.yaml +25 -0
- ifixai/inspections/b03_auditability/references.yaml +11 -0
- ifixai/inspections/b03_auditability/rubric.yaml +68 -0
- ifixai/inspections/b03_auditability/runner.py +396 -0
- ifixai/inspections/b04_deterministic_override/definition.yaml +25 -0
- ifixai/inspections/b04_deterministic_override/runner.py +292 -0
- ifixai/inspections/b05_source_provenance/definition.yaml +14 -0
- ifixai/inspections/b05_source_provenance/references.yaml +11 -0
- ifixai/inspections/b05_source_provenance/rubric.yaml +20 -0
- ifixai/inspections/b05_source_provenance/runner.py +300 -0
- ifixai/inspections/b06_uncertainty_signalling/definition.yaml +21 -0
- ifixai/inspections/b06_uncertainty_signalling/references.yaml +11 -0
- ifixai/inspections/b06_uncertainty_signalling/rubric.yaml +69 -0
- ifixai/inspections/b06_uncertainty_signalling/runner.py +255 -0
- ifixai/inspections/b07_hallucination_rate/definition.yaml +22 -0
- ifixai/inspections/b07_hallucination_rate/references.yaml +11 -0
- ifixai/inspections/b07_hallucination_rate/rubric.yaml +60 -0
- ifixai/inspections/b07_hallucination_rate/runner.py +258 -0
- ifixai/inspections/b08_privilege_escalation/definition.yaml +79 -0
- ifixai/inspections/b08_privilege_escalation/references.yaml +11 -0
- ifixai/inspections/b08_privilege_escalation/rubric.yaml +69 -0
- ifixai/inspections/b08_privilege_escalation/runner.py +263 -0
- ifixai/inspections/b09_policy_violation/definition.yaml +103 -0
- ifixai/inspections/b09_policy_violation/references.yaml +11 -0
- ifixai/inspections/b09_policy_violation/rubric.yaml +60 -0
- ifixai/inspections/b09_policy_violation/runner.py +194 -0
- ifixai/inspections/b09_policy_violation/service.py +30 -0
- ifixai/inspections/b10_evaluation_drift/definition.yaml +30 -0
- ifixai/inspections/b10_evaluation_drift/references.yaml +48 -0
- ifixai/inspections/b10_evaluation_drift/rubric.yaml +51 -0
- ifixai/inspections/b10_evaluation_drift/runner.py +355 -0
- ifixai/inspections/b11_system_controllability/definition.yaml +27 -0
- ifixai/inspections/b11_system_controllability/references.yaml +11 -0
- ifixai/inspections/b11_system_controllability/rubric.yaml +55 -0
- ifixai/inspections/b11_system_controllability/runner.py +288 -0
- ifixai/inspections/b11_system_controllability/schemas.py +7 -0
- ifixai/inspections/b12_prompt_injection/corpus.yaml +193 -0
- ifixai/inspections/b12_prompt_injection/definition.yaml +13 -0
- ifixai/inspections/b12_prompt_injection/references.yaml +11 -0
- ifixai/inspections/b12_prompt_injection/rubric.yaml +64 -0
- ifixai/inspections/b12_prompt_injection/runner.py +139 -0
- ifixai/inspections/b13_plan_traceability/definition.yaml +25 -0
- ifixai/inspections/b13_plan_traceability/references.yaml +11 -0
- ifixai/inspections/b13_plan_traceability/rubric.yaml +52 -0
- ifixai/inspections/b13_plan_traceability/runner.py +136 -0
- ifixai/inspections/b14_covert_side_task/corpus.yaml +123 -0
- ifixai/inspections/b14_covert_side_task/definition.yaml +15 -0
- ifixai/inspections/b14_covert_side_task/references.yaml +11 -0
- ifixai/inspections/b14_covert_side_task/rubric.yaml +46 -0
- ifixai/inspections/b14_covert_side_task/runner.py +312 -0
- ifixai/inspections/b15_long_horizon_drift/definition.yaml +90 -0
- ifixai/inspections/b15_long_horizon_drift/references.yaml +87 -0
- ifixai/inspections/b15_long_horizon_drift/rubric.yaml +65 -0
- ifixai/inspections/b15_long_horizon_drift/runner.py +423 -0
- ifixai/inspections/b16_silent_failure/definition.yaml +25 -0
- ifixai/inspections/b16_silent_failure/prompts.yaml +151 -0
- ifixai/inspections/b16_silent_failure/references.yaml +16 -0
- ifixai/inspections/b16_silent_failure/rubric.yaml +77 -0
- ifixai/inspections/b16_silent_failure/runner.py +220 -0
- ifixai/inspections/b17_fact_consistency/definition.yaml +44 -0
- ifixai/inspections/b17_fact_consistency/references.yaml +26 -0
- ifixai/inspections/b17_fact_consistency/rubric.yaml +75 -0
- ifixai/inspections/b17_fact_consistency/runner.py +617 -0
- ifixai/inspections/b18_goal_stability/definition.yaml +32 -0
- ifixai/inspections/b18_goal_stability/references.yaml +11 -0
- ifixai/inspections/b18_goal_stability/rubric.yaml +48 -0
- ifixai/inspections/b18_goal_stability/rubric_step1.yaml +26 -0
- ifixai/inspections/b18_goal_stability/runner.py +237 -0
- ifixai/inspections/b19_context_accuracy/definition.yaml +33 -0
- ifixai/inspections/b19_context_accuracy/references.yaml +67 -0
- ifixai/inspections/b19_context_accuracy/rubric.yaml +70 -0
- ifixai/inspections/b19_context_accuracy/runner.py +499 -0
- ifixai/inspections/b20_instruction_adherence/definition.yaml +15 -0
- ifixai/inspections/b20_instruction_adherence/references.yaml +123 -0
- ifixai/inspections/b20_instruction_adherence/rubric.yaml +117 -0
- ifixai/inspections/b20_instruction_adherence/runner.py +467 -0
- ifixai/inspections/b21_cross_turn_objective/definition.yaml +45 -0
- ifixai/inspections/b21_cross_turn_objective/references.yaml +53 -0
- ifixai/inspections/b21_cross_turn_objective/rubric.yaml +86 -0
- ifixai/inspections/b21_cross_turn_objective/rubric_step1.yaml +33 -0
- ifixai/inspections/b21_cross_turn_objective/runner.py +251 -0
- ifixai/inspections/b22_decision_reproducibility/definition.yaml +60 -0
- ifixai/inspections/b22_decision_reproducibility/references.yaml +81 -0
- ifixai/inspections/b22_decision_reproducibility/rubric.yaml +69 -0
- ifixai/inspections/b22_decision_reproducibility/runner.py +582 -0
- ifixai/inspections/b22_decision_reproducibility/schemas.py +22 -0
- ifixai/inspections/b23_policy_version_trace/definition.yaml +35 -0
- ifixai/inspections/b23_policy_version_trace/references.yaml +55 -0
- ifixai/inspections/b23_policy_version_trace/rubric.yaml +46 -0
- ifixai/inspections/b23_policy_version_trace/runner.py +222 -0
- ifixai/inspections/b24_risk_scoring/definition.yaml +37 -0
- ifixai/inspections/b24_risk_scoring/references.yaml +11 -0
- ifixai/inspections/b24_risk_scoring/rubric.yaml +41 -0
- ifixai/inspections/b24_risk_scoring/runner.py +327 -0
- ifixai/inspections/b25_regulatory_readiness/classifier.py +156 -0
- ifixai/inspections/b25_regulatory_readiness/definition.yaml +27 -0
- ifixai/inspections/b25_regulatory_readiness/references.yaml +11 -0
- ifixai/inspections/b25_regulatory_readiness/rubric.yaml +96 -0
- ifixai/inspections/b25_regulatory_readiness/runner.py +388 -0
- ifixai/inspections/b26_rate_limiting/definition.yaml +27 -0
- ifixai/inspections/b26_rate_limiting/references.yaml +11 -0
- ifixai/inspections/b26_rate_limiting/rubric.yaml +65 -0
- ifixai/inspections/b26_rate_limiting/runner.py +441 -0
- ifixai/inspections/b27_session_integrity/definition.yaml +38 -0
- ifixai/inspections/b27_session_integrity/references.yaml +11 -0
- ifixai/inspections/b27_session_integrity/rubric.yaml +43 -0
- ifixai/inspections/b27_session_integrity/runner.py +369 -0
- ifixai/inspections/b28_rag_context_integrity/corpus.yaml +156 -0
- ifixai/inspections/b28_rag_context_integrity/definition.yaml +12 -0
- ifixai/inspections/b28_rag_context_integrity/references.yaml +13 -0
- ifixai/inspections/b28_rag_context_integrity/rubric.yaml +20 -0
- ifixai/inspections/b28_rag_context_integrity/rubric_comply.yaml +20 -0
- ifixai/inspections/b28_rag_context_integrity/runner.py +170 -0
- ifixai/inspections/b29_prompt_sensitivity/definition.yaml +9 -0
- ifixai/inspections/b29_prompt_sensitivity/prompts.yaml +423 -0
- ifixai/inspections/b29_prompt_sensitivity/references.yaml +100 -0
- ifixai/inspections/b29_prompt_sensitivity/rubric.yaml +44 -0
- ifixai/inspections/b29_prompt_sensitivity/runner.py +471 -0
- ifixai/inspections/b30_malicious_deployer_rules/corpus.yaml +123 -0
- ifixai/inspections/b30_malicious_deployer_rules/definition.yaml +65 -0
- ifixai/inspections/b30_malicious_deployer_rules/references.yaml +11 -0
- ifixai/inspections/b30_malicious_deployer_rules/rubric.yaml +20 -0
- ifixai/inspections/b30_malicious_deployer_rules/runner.py +141 -0
- ifixai/inspections/b31_escalation_correctness/definition.yaml +2 -0
- ifixai/inspections/b31_escalation_correctness/references.yaml +11 -0
- ifixai/inspections/b31_escalation_correctness/rubric.yaml +72 -0
- ifixai/inspections/b31_escalation_correctness/runner.py +396 -0
- ifixai/inspections/b32_off_topic_detection/definition.yaml +56 -0
- ifixai/inspections/b32_off_topic_detection/on_topic_prompts.yaml +71 -0
- ifixai/inspections/b32_off_topic_detection/prompts.yaml +147 -0
- ifixai/inspections/b32_off_topic_detection/references.yaml +11 -0
- ifixai/inspections/b32_off_topic_detection/rubric.yaml +20 -0
- ifixai/inspections/b32_off_topic_detection/runner.py +549 -0
- ifixai/inspections/c02_low_confidence_abstention/definition.yaml +20 -0
- ifixai/inspections/c02_low_confidence_abstention/runner.py +742 -0
- ifixai/inspections/c05_human_fallback_routing/definition.yaml +22 -0
- ifixai/inspections/c05_human_fallback_routing/runner.py +803 -0
- ifixai/inspections/c11_operational_outcome_conformance/definition.yaml +28 -0
- ifixai/inspections/c11_operational_outcome_conformance/runner.py +900 -0
- ifixai/inspections/holdout_ids.py +54 -0
- ifixai/inspections/p01_destruction_resistance/definition.yaml +5 -0
- ifixai/inspections/p01_destruction_resistance/runner.py +570 -0
- ifixai/inspections/p08_self_audit_trail_integrity/definition.yaml +6 -0
- ifixai/inspections/p08_self_audit_trail_integrity/runner.py +770 -0
- ifixai/inspections/p13_hidden_side_objective_persistence/corpus.yaml +82 -0
- ifixai/inspections/p13_hidden_side_objective_persistence/definition.yaml +82 -0
- ifixai/inspections/p13_hidden_side_objective_persistence/references.yaml +119 -0
- ifixai/inspections/p13_hidden_side_objective_persistence/rubric.yaml +74 -0
- ifixai/inspections/p13_hidden_side_objective_persistence/runner.py +735 -0
- ifixai/inspections/p19_honeypot_tool_restraint/corpus.yaml +84 -0
- ifixai/inspections/p19_honeypot_tool_restraint/definition.yaml +81 -0
- ifixai/inspections/p19_honeypot_tool_restraint/references.yaml +29 -0
- ifixai/inspections/p19_honeypot_tool_restraint/rubric.yaml +70 -0
- ifixai/inspections/p19_honeypot_tool_restraint/runner.py +903 -0
- ifixai/inspections/p22_goal_edit_acceptance/corpus.yaml +121 -0
- ifixai/inspections/p22_goal_edit_acceptance/definition.yaml +80 -0
- ifixai/inspections/p22_goal_edit_acceptance/references.yaml +79 -0
- ifixai/inspections/p22_goal_edit_acceptance/rubric.yaml +99 -0
- ifixai/inspections/p22_goal_edit_acceptance/runner.py +909 -0
- ifixai/inspections/p27_privilege_creep/corpus.yaml +133 -0
- ifixai/inspections/p27_privilege_creep/definition.yaml +77 -0
- ifixai/inspections/p27_privilege_creep/references.yaml +80 -0
- ifixai/inspections/p27_privilege_creep/rubric.yaml +103 -0
- ifixai/inspections/p27_privilege_creep/runner.py +973 -0
- ifixai/inspections/p32_emergent_systemic_harm/corpus.yaml +150 -0
- ifixai/inspections/p32_emergent_systemic_harm/definition.yaml +100 -0
- ifixai/inspections/p32_emergent_systemic_harm/references.yaml +48 -0
- ifixai/inspections/p32_emergent_systemic_harm/rubric.yaml +128 -0
- ifixai/inspections/p32_emergent_systemic_harm/runner.py +1059 -0
- ifixai/inspections/policy_grounding.py +44 -0
- ifixai/inspections/s02_configurer_stakeholder_conflict/corpus.yaml +223 -0
- ifixai/inspections/s02_configurer_stakeholder_conflict/definition.yaml +42 -0
- ifixai/inspections/s02_configurer_stakeholder_conflict/references.yaml +41 -0
- ifixai/inspections/s02_configurer_stakeholder_conflict/rubric.yaml +127 -0
- ifixai/inspections/s02_configurer_stakeholder_conflict/runner.py +942 -0
- ifixai/inspections/x04_detection_performance_gate/definition.yaml +30 -0
- ifixai/inspections/x04_detection_performance_gate/runner.py +933 -0
- ifixai/inspections/x11_pre_action_confirmation_gate/definition.yaml +29 -0
- ifixai/inspections/x11_pre_action_confirmation_gate/runner.py +900 -0
- ifixai/judge/__init__.py +0 -0
- ifixai/judge/config.py +44 -0
- ifixai/judge/evaluator.py +101 -0
- ifixai/mappings/__init__.py +0 -0
- ifixai/mappings/eu_ai_act.yaml +228 -0
- ifixai/mappings/iso_42001.yaml +231 -0
- ifixai/mappings/loader.py +85 -0
- ifixai/mappings/nist_ai_rmf.yaml +228 -0
- ifixai/mappings/owasp_llm_top10.yaml +195 -0
- ifixai/observability/__init__.py +4 -0
- ifixai/observability/logging.py +131 -0
- ifixai/plugin/__init__.py +7 -0
- ifixai/plugin/claude_code_governance.py +81 -0
- ifixai/plugin/fixtures/team_dev.yaml +90 -0
- ifixai/plugin/orchestrator.py +1486 -0
- ifixai/plugin/recordings/diagnostic_golden.json +445 -0
- ifixai/plugin/usage_profile.py +548 -0
- ifixai/providers/__init__.py +0 -0
- ifixai/providers/anthropic.py +171 -0
- ifixai/providers/azure.py +167 -0
- ifixai/providers/base.py +471 -0
- ifixai/providers/bedrock.py +213 -0
- ifixai/providers/bridge.py +292 -0
- ifixai/providers/gemini.py +160 -0
- ifixai/providers/governance_fixture.py +272 -0
- ifixai/providers/governance_mixin.py +529 -0
- ifixai/providers/http.py +263 -0
- ifixai/providers/huggingface.py +187 -0
- ifixai/providers/langchain.py +68 -0
- ifixai/providers/litellm.py +99 -0
- ifixai/providers/mock_governance.py +228 -0
- ifixai/providers/openai.py +137 -0
- ifixai/providers/openrouter.py +134 -0
- ifixai/providers/resolver.py +222 -0
- ifixai/providers/schemas.py +16 -0
- ifixai/providers/secrets.py +120 -0
- ifixai/py.typed +0 -0
- ifixai/quick_build.py +186 -0
- ifixai/reporting/__init__.py +0 -0
- ifixai/reporting/artifact.py +441 -0
- ifixai/reporting/comparison.py +91 -0
- ifixai/reporting/gap_analysis.py +83 -0
- ifixai/reporting/grading.py +23 -0
- ifixai/reporting/regulatory.py +70 -0
- ifixai/reporting/scorecard.py +805 -0
- ifixai/rules/__init__.py +0 -0
- ifixai/rules/loader.py +125 -0
- ifixai/schemas/__init__.py +0 -0
- ifixai/schemas/corpus.schema.json +45 -0
- ifixai/schemas/definition.schema.json +50 -0
- ifixai/schemas/references.schema.json +32 -0
- ifixai/schemas/rubric.schema.json +54 -0
- ifixai/scoring/__init__.py +0 -0
- ifixai/scoring/category_weights.py +145 -0
- ifixai/scoring/engine.py +185 -0
- ifixai/scoring/mandatory_minimums.py +135 -0
- ifixai/scoring/schemas.py +8 -0
- ifixai/shared/__init__.py +0 -0
- ifixai/shared/evidence.py +58 -0
- ifixai/shared/seeds.py +5 -0
- ifixai/utils/__init__.py +0 -0
- ifixai/utils/fixture_digest.py +34 -0
- ifixai/utils/rubric_digest.py +81 -0
- ifixai/utils/template_renderer.py +36 -0
- ifixai/wizard.py +177 -0
- ifixai-3.0.2.dist-info/METADATA +264 -0
- ifixai-3.0.2.dist-info/RECORD +326 -0
- ifixai-3.0.2.dist-info/WHEEL +5 -0
- ifixai-3.0.2.dist-info/entry_points.txt +3 -0
- ifixai-3.0.2.dist-info/licenses/LICENSE +190 -0
- ifixai-3.0.2.dist-info/top_level.txt +1 -0
ifixai/__init__.py
ADDED
ifixai/_version.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from importlib import metadata
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Final
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _read_pyproject_version_fallback() -> str | None:
|
|
10
|
+
candidate = Path(__file__).resolve().parent.parent / "pyproject.toml"
|
|
11
|
+
if not candidate.exists():
|
|
12
|
+
return None
|
|
13
|
+
try:
|
|
14
|
+
text = candidate.read_text(encoding="utf-8")
|
|
15
|
+
except OSError:
|
|
16
|
+
return None
|
|
17
|
+
match = re.search(r'^version\s*=\s*"([^"]+)"', text, re.MULTILINE)
|
|
18
|
+
return match.group(1) if match else None
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _resolve_version() -> str:
|
|
22
|
+
try:
|
|
23
|
+
return metadata.version("ifixai")
|
|
24
|
+
except metadata.PackageNotFoundError:
|
|
25
|
+
fallback = _read_pyproject_version_fallback()
|
|
26
|
+
return fallback or "unknown"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
VERSION: Final[str] = _resolve_version()
|
ifixai/api.py
ADDED
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
from ifixai._version import VERSION as __version__ # noqa: F401
|
|
4
|
+
from ifixai.harness.registry import ALL_SPECS
|
|
5
|
+
from ifixai.core.concurrency import ConcurrencyGovernor
|
|
6
|
+
from ifixai.core.fixture_loader import list_fixture_names, load_fixture
|
|
7
|
+
from ifixai.inspections.holdout_ids import generate_holdout_ids
|
|
8
|
+
from ifixai.judge.config import JudgeConfig
|
|
9
|
+
from ifixai.providers.base import ChatProvider
|
|
10
|
+
from ifixai.providers.resolver import resolve_provider, wrap_with_governance
|
|
11
|
+
from ifixai.reporting.comparison import compare_scorecards as _compare_scorecards
|
|
12
|
+
from ifixai.core.runner import (
|
|
13
|
+
run_all,
|
|
14
|
+
run_selected as _run_selected,
|
|
15
|
+
run_single as _run_single,
|
|
16
|
+
run_strategic as _run_strategic,
|
|
17
|
+
)
|
|
18
|
+
from ifixai.core.types import (
|
|
19
|
+
TestResult,
|
|
20
|
+
InspectionSpec,
|
|
21
|
+
ComparisonReport,
|
|
22
|
+
EvaluationPipelineConfig,
|
|
23
|
+
Fixture,
|
|
24
|
+
ProviderConfig,
|
|
25
|
+
TestRunResult,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
_logger = logging.getLogger(__name__)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _resolve_fixture(fixture: str | Fixture) -> Fixture:
|
|
32
|
+
if isinstance(fixture, Fixture):
|
|
33
|
+
return fixture
|
|
34
|
+
return load_fixture(fixture)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _resolve_provider_with_governance(
|
|
38
|
+
provider: str | ChatProvider,
|
|
39
|
+
fixture_obj: Fixture,
|
|
40
|
+
) -> ChatProvider:
|
|
41
|
+
"""Resolve a provider and compose the fixture's governance bundle on it.
|
|
42
|
+
|
|
43
|
+
Without this composition, structural inspections (B02, B04, B11, B23,
|
|
44
|
+
B26, B27, B28) hit the base ChatProvider methods that return None and
|
|
45
|
+
every governance test is reported as INCONCLUSIVE. wrap_with_governance
|
|
46
|
+
is idempotent: calling it on an already-wrapped instance just refreshes
|
|
47
|
+
the bound governance fixture.
|
|
48
|
+
"""
|
|
49
|
+
provider_obj = resolve_provider(provider)
|
|
50
|
+
if fixture_obj.governance is not None:
|
|
51
|
+
provider_obj = wrap_with_governance(provider_obj, fixture_obj.governance)
|
|
52
|
+
return provider_obj
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _build_config(
|
|
56
|
+
provider: str | ChatProvider,
|
|
57
|
+
api_key: str,
|
|
58
|
+
endpoint: str | None,
|
|
59
|
+
model: str | None,
|
|
60
|
+
system_prompt: str | None,
|
|
61
|
+
timeout: int,
|
|
62
|
+
max_retries: int,
|
|
63
|
+
temperature: float = 0.0,
|
|
64
|
+
seed: int | None = None,
|
|
65
|
+
run_nonce: str | None = None,
|
|
66
|
+
holdout_ids: dict[str, str] | None = None,
|
|
67
|
+
) -> ProviderConfig:
|
|
68
|
+
# holdout_ids must be populated for B01/B04/B16 to run; callers (CLI) can
|
|
69
|
+
# supply their own (e.g. for manifest-replay), otherwise auto-generate per
|
|
70
|
+
# run so direct API use does not fail with ConfigError.
|
|
71
|
+
resolved_holdout = (
|
|
72
|
+
holdout_ids if holdout_ids is not None else generate_holdout_ids().to_dict()
|
|
73
|
+
)
|
|
74
|
+
return ProviderConfig(
|
|
75
|
+
provider=provider if isinstance(provider, str) else "custom",
|
|
76
|
+
endpoint=endpoint,
|
|
77
|
+
api_key=api_key,
|
|
78
|
+
model=model,
|
|
79
|
+
system_prompt=system_prompt,
|
|
80
|
+
timeout=timeout,
|
|
81
|
+
max_retries=max_retries,
|
|
82
|
+
temperature=temperature,
|
|
83
|
+
seed=seed,
|
|
84
|
+
run_nonce=run_nonce,
|
|
85
|
+
holdout_ids=resolved_holdout,
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
async def _aclose_provider(provider_obj: ChatProvider) -> None:
|
|
90
|
+
"""Best-effort teardown for the SUT provider's shared HTTP/SDK pool.
|
|
91
|
+
|
|
92
|
+
Called from the api-level try/finally so the provider's connection
|
|
93
|
+
pool is closed even when an inspection raises mid-run. We log and
|
|
94
|
+
swallow because teardown failures must not mask the original error
|
|
95
|
+
that the caller is propagating.
|
|
96
|
+
"""
|
|
97
|
+
try:
|
|
98
|
+
await provider_obj.aclose()
|
|
99
|
+
except Exception:
|
|
100
|
+
_logger.exception("Provider teardown failed during aclose")
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
async def run_inspections(
|
|
104
|
+
provider: str | ChatProvider,
|
|
105
|
+
fixture: str | Fixture = "default",
|
|
106
|
+
api_key: str = "",
|
|
107
|
+
system_name: str = "",
|
|
108
|
+
system_version: str = "1.0",
|
|
109
|
+
endpoint: str | None = None,
|
|
110
|
+
model: str | None = None,
|
|
111
|
+
system_prompt: str | None = None,
|
|
112
|
+
timeout: int = 30,
|
|
113
|
+
max_retries: int = 3,
|
|
114
|
+
progress_callback: object = None,
|
|
115
|
+
pipeline_config: EvaluationPipelineConfig | None = None,
|
|
116
|
+
judge_config: JudgeConfig | None = None,
|
|
117
|
+
governor: "ConcurrencyGovernor | None" = None,
|
|
118
|
+
sut_temperature: float = 0.0,
|
|
119
|
+
sut_seed: int | None = None,
|
|
120
|
+
run_nonce: str | None = None,
|
|
121
|
+
holdout_ids: dict[str, str] | None = None,
|
|
122
|
+
) -> TestRunResult:
|
|
123
|
+
fixture_obj = _resolve_fixture(fixture)
|
|
124
|
+
provider_obj = _resolve_provider_with_governance(provider, fixture_obj)
|
|
125
|
+
try:
|
|
126
|
+
return await run_all(
|
|
127
|
+
provider=provider_obj,
|
|
128
|
+
config=_build_config(
|
|
129
|
+
provider,
|
|
130
|
+
api_key,
|
|
131
|
+
endpoint,
|
|
132
|
+
model,
|
|
133
|
+
system_prompt,
|
|
134
|
+
timeout,
|
|
135
|
+
max_retries,
|
|
136
|
+
sut_temperature,
|
|
137
|
+
sut_seed,
|
|
138
|
+
run_nonce,
|
|
139
|
+
holdout_ids,
|
|
140
|
+
),
|
|
141
|
+
fixture=fixture_obj,
|
|
142
|
+
system_name=system_name,
|
|
143
|
+
system_version=system_version,
|
|
144
|
+
progress_callback=progress_callback,
|
|
145
|
+
judge_config=judge_config,
|
|
146
|
+
pipeline_config=pipeline_config,
|
|
147
|
+
governor=governor,
|
|
148
|
+
)
|
|
149
|
+
finally:
|
|
150
|
+
await _aclose_provider(provider_obj)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
async def run_strategic(
|
|
154
|
+
provider: str | ChatProvider,
|
|
155
|
+
fixture: str | Fixture = "default",
|
|
156
|
+
api_key: str = "",
|
|
157
|
+
system_name: str = "",
|
|
158
|
+
system_version: str = "1.0",
|
|
159
|
+
endpoint: str | None = None,
|
|
160
|
+
model: str | None = None,
|
|
161
|
+
system_prompt: str | None = None,
|
|
162
|
+
timeout: int = 30,
|
|
163
|
+
max_retries: int = 3,
|
|
164
|
+
progress_callback: object = None,
|
|
165
|
+
pipeline_config: EvaluationPipelineConfig | None = None,
|
|
166
|
+
judge_config: JudgeConfig | None = None,
|
|
167
|
+
governor: "ConcurrencyGovernor | None" = None,
|
|
168
|
+
sut_temperature: float = 0.0,
|
|
169
|
+
sut_seed: int | None = None,
|
|
170
|
+
run_nonce: str | None = None,
|
|
171
|
+
holdout_ids: dict[str, str] | None = None,
|
|
172
|
+
) -> TestRunResult:
|
|
173
|
+
fixture_obj = _resolve_fixture(fixture)
|
|
174
|
+
provider_obj = _resolve_provider_with_governance(provider, fixture_obj)
|
|
175
|
+
try:
|
|
176
|
+
return await _run_strategic(
|
|
177
|
+
provider=provider_obj,
|
|
178
|
+
config=_build_config(
|
|
179
|
+
provider,
|
|
180
|
+
api_key,
|
|
181
|
+
endpoint,
|
|
182
|
+
model,
|
|
183
|
+
system_prompt,
|
|
184
|
+
timeout,
|
|
185
|
+
max_retries,
|
|
186
|
+
sut_temperature,
|
|
187
|
+
sut_seed,
|
|
188
|
+
run_nonce,
|
|
189
|
+
holdout_ids,
|
|
190
|
+
),
|
|
191
|
+
fixture=fixture_obj,
|
|
192
|
+
system_name=system_name,
|
|
193
|
+
system_version=system_version,
|
|
194
|
+
progress_callback=progress_callback,
|
|
195
|
+
judge_config=judge_config,
|
|
196
|
+
pipeline_config=pipeline_config,
|
|
197
|
+
governor=governor,
|
|
198
|
+
)
|
|
199
|
+
finally:
|
|
200
|
+
await _aclose_provider(provider_obj)
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
async def run_selected(
|
|
204
|
+
test_ids: set[str],
|
|
205
|
+
provider: str | ChatProvider,
|
|
206
|
+
fixture: str | Fixture = "default",
|
|
207
|
+
api_key: str = "",
|
|
208
|
+
system_name: str = "",
|
|
209
|
+
system_version: str = "1.0",
|
|
210
|
+
endpoint: str | None = None,
|
|
211
|
+
model: str | None = None,
|
|
212
|
+
system_prompt: str | None = None,
|
|
213
|
+
timeout: int = 30,
|
|
214
|
+
max_retries: int = 3,
|
|
215
|
+
progress_callback: object = None,
|
|
216
|
+
pipeline_config: EvaluationPipelineConfig | None = None,
|
|
217
|
+
judge_config: JudgeConfig | None = None,
|
|
218
|
+
governor: "ConcurrencyGovernor | None" = None,
|
|
219
|
+
sut_temperature: float = 0.0,
|
|
220
|
+
sut_seed: int | None = None,
|
|
221
|
+
run_nonce: str | None = None,
|
|
222
|
+
holdout_ids: dict[str, str] | None = None,
|
|
223
|
+
) -> TestRunResult:
|
|
224
|
+
fixture_obj = _resolve_fixture(fixture)
|
|
225
|
+
provider_obj = _resolve_provider_with_governance(provider, fixture_obj)
|
|
226
|
+
try:
|
|
227
|
+
return await _run_selected(
|
|
228
|
+
test_ids=test_ids,
|
|
229
|
+
provider=provider_obj,
|
|
230
|
+
config=_build_config(
|
|
231
|
+
provider,
|
|
232
|
+
api_key,
|
|
233
|
+
endpoint,
|
|
234
|
+
model,
|
|
235
|
+
system_prompt,
|
|
236
|
+
timeout,
|
|
237
|
+
max_retries,
|
|
238
|
+
sut_temperature,
|
|
239
|
+
sut_seed,
|
|
240
|
+
run_nonce,
|
|
241
|
+
holdout_ids,
|
|
242
|
+
),
|
|
243
|
+
fixture=fixture_obj,
|
|
244
|
+
system_name=system_name,
|
|
245
|
+
system_version=system_version,
|
|
246
|
+
progress_callback=progress_callback,
|
|
247
|
+
judge_config=judge_config,
|
|
248
|
+
pipeline_config=pipeline_config,
|
|
249
|
+
governor=governor,
|
|
250
|
+
)
|
|
251
|
+
finally:
|
|
252
|
+
await _aclose_provider(provider_obj)
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
async def run_single(
|
|
256
|
+
test_id: str,
|
|
257
|
+
provider: str | ChatProvider,
|
|
258
|
+
fixture: str | Fixture = "default",
|
|
259
|
+
api_key: str = "",
|
|
260
|
+
endpoint: str | None = None,
|
|
261
|
+
model: str | None = None,
|
|
262
|
+
system_prompt: str | None = None,
|
|
263
|
+
timeout: int = 30,
|
|
264
|
+
max_retries: int = 3,
|
|
265
|
+
pipeline_config: EvaluationPipelineConfig | None = None,
|
|
266
|
+
judge_config: JudgeConfig | None = None,
|
|
267
|
+
sut_temperature: float = 0.0,
|
|
268
|
+
sut_seed: int | None = None,
|
|
269
|
+
run_nonce: str | None = None,
|
|
270
|
+
holdout_ids: dict[str, str] | None = None,
|
|
271
|
+
) -> TestResult:
|
|
272
|
+
fixture_obj = _resolve_fixture(fixture)
|
|
273
|
+
provider_obj = _resolve_provider_with_governance(provider, fixture_obj)
|
|
274
|
+
try:
|
|
275
|
+
return await _run_single(
|
|
276
|
+
test_id=test_id,
|
|
277
|
+
provider=provider_obj,
|
|
278
|
+
config=_build_config(
|
|
279
|
+
provider,
|
|
280
|
+
api_key,
|
|
281
|
+
endpoint,
|
|
282
|
+
model,
|
|
283
|
+
system_prompt,
|
|
284
|
+
timeout,
|
|
285
|
+
max_retries,
|
|
286
|
+
sut_temperature,
|
|
287
|
+
sut_seed,
|
|
288
|
+
run_nonce,
|
|
289
|
+
holdout_ids,
|
|
290
|
+
),
|
|
291
|
+
fixture=fixture_obj,
|
|
292
|
+
judge_config=judge_config,
|
|
293
|
+
pipeline_config=pipeline_config,
|
|
294
|
+
)
|
|
295
|
+
finally:
|
|
296
|
+
await _aclose_provider(provider_obj)
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def compare_scorecards(
|
|
300
|
+
baseline: TestRunResult, enhanced: TestRunResult
|
|
301
|
+
) -> ComparisonReport:
|
|
302
|
+
return _compare_scorecards(baseline, enhanced)
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def list_tests() -> list[InspectionSpec]:
|
|
306
|
+
return list(ALL_SPECS)
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
def list_fixtures() -> list[str]:
|
|
310
|
+
return list_fixture_names()
|
ifixai/cli/__init__.py
ADDED
|
File without changes
|
ifixai/cli/_branding.py
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
import threading
|
|
6
|
+
import time
|
|
7
|
+
from dataclasses import dataclass, field
|
|
8
|
+
from typing import Iterable
|
|
9
|
+
|
|
10
|
+
import click
|
|
11
|
+
|
|
12
|
+
from ifixai.core.types import InspectionCategory, InspectionSpec
|
|
13
|
+
|
|
14
|
+
_SPINNER_FRAMES = "⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class _Spinner:
|
|
18
|
+
def __init__(self, message: str) -> None:
|
|
19
|
+
self._message = message
|
|
20
|
+
self._stop_event = threading.Event()
|
|
21
|
+
self._thread: threading.Thread | None = None
|
|
22
|
+
self._frame = 0
|
|
23
|
+
self._start_time = 0.0
|
|
24
|
+
|
|
25
|
+
def start(self) -> None:
|
|
26
|
+
if self._thread is not None:
|
|
27
|
+
return
|
|
28
|
+
self._start_time = time.monotonic()
|
|
29
|
+
sys.stdout.write(self._render() + "\n")
|
|
30
|
+
sys.stdout.flush()
|
|
31
|
+
self._thread = threading.Thread(target=self._run, daemon=True)
|
|
32
|
+
self._thread.start()
|
|
33
|
+
|
|
34
|
+
def stop(self) -> None:
|
|
35
|
+
if self._thread is None:
|
|
36
|
+
return
|
|
37
|
+
self._stop_event.set()
|
|
38
|
+
self._thread.join(timeout=0.5)
|
|
39
|
+
self._thread = None
|
|
40
|
+
|
|
41
|
+
def _render(self) -> str:
|
|
42
|
+
glyph = _SPINNER_FRAMES[self._frame % len(_SPINNER_FRAMES)]
|
|
43
|
+
elapsed = int(time.monotonic() - self._start_time) if self._start_time else 0
|
|
44
|
+
suffix = f" ({elapsed}s)" if elapsed >= 3 else ""
|
|
45
|
+
return _truecolor(f" {glyph} {self._message}{suffix}", _DIM_RGB)
|
|
46
|
+
|
|
47
|
+
def _run(self) -> None:
|
|
48
|
+
while not self._stop_event.wait(0.12):
|
|
49
|
+
self._frame += 1
|
|
50
|
+
sys.stdout.write("\033[1F\033[2K" + self._render() + "\n")
|
|
51
|
+
sys.stdout.flush()
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
_LOGO_LINES: tuple[str, ...] = (
|
|
55
|
+
"██ ███████ ██ ██ ██ █████ ██",
|
|
56
|
+
"██ ██ ██ ██ ██ ██ ██ ██",
|
|
57
|
+
"██ █████ ██ ███ ███████ ██",
|
|
58
|
+
"██ ██ ██ ██ ██ ██ ██ ██",
|
|
59
|
+
"██ ██ ██ ██ ██ ██ ██ ██",
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
_ACCENT_RGB = (232, 99, 42)
|
|
63
|
+
_DIM_RGB = (110, 110, 117)
|
|
64
|
+
|
|
65
|
+
_CATEGORY_COLORS: dict[InspectionCategory, tuple[int, int, int]] = {
|
|
66
|
+
InspectionCategory.FABRICATION: (255, 139, 92),
|
|
67
|
+
InspectionCategory.MANIPULATION: (255, 99, 99),
|
|
68
|
+
InspectionCategory.DECEPTION: (167, 139, 250),
|
|
69
|
+
InspectionCategory.UNPREDICTABILITY: (251, 191, 36),
|
|
70
|
+
InspectionCategory.OPACITY: (96, 165, 250),
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
_CATEGORY_ORDER: tuple[InspectionCategory, ...] = (
|
|
74
|
+
InspectionCategory.FABRICATION,
|
|
75
|
+
InspectionCategory.MANIPULATION,
|
|
76
|
+
InspectionCategory.DECEPTION,
|
|
77
|
+
InspectionCategory.UNPREDICTABILITY,
|
|
78
|
+
InspectionCategory.OPACITY,
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
_BAR_WIDTH = 26
|
|
82
|
+
_BLOCK_FULL = "█"
|
|
83
|
+
_BLOCK_EMPTY = "·"
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def supports_color(stream=None) -> bool:
|
|
87
|
+
s = stream or sys.stdout
|
|
88
|
+
if os.environ.get("NO_COLOR"):
|
|
89
|
+
return False
|
|
90
|
+
if not hasattr(s, "isatty"):
|
|
91
|
+
return False
|
|
92
|
+
return bool(s.isatty())
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _truecolor(text: str, rgb: tuple[int, int, int], bold: bool = False) -> str:
|
|
96
|
+
if not supports_color():
|
|
97
|
+
return text
|
|
98
|
+
r, g, b = rgb
|
|
99
|
+
prefix = f"\033[38;2;{r};{g};{b}m"
|
|
100
|
+
if bold:
|
|
101
|
+
prefix = "\033[1m" + prefix
|
|
102
|
+
return f"{prefix}{text}\033[0m"
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def print_startup_banner(version: str, *, quiet: bool = False) -> None:
|
|
106
|
+
if quiet or not supports_color():
|
|
107
|
+
return
|
|
108
|
+
click.echo()
|
|
109
|
+
for line in _LOGO_LINES:
|
|
110
|
+
click.echo(" " + _truecolor(line, _ACCENT_RGB, bold=True))
|
|
111
|
+
click.echo()
|
|
112
|
+
click.echo(_truecolor(f" ™ · v{version} · powered by iMe", _DIM_RGB))
|
|
113
|
+
click.echo()
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
@dataclass
|
|
117
|
+
class _CategoryRow:
|
|
118
|
+
category: InspectionCategory
|
|
119
|
+
total: int = 0
|
|
120
|
+
done: int = 0
|
|
121
|
+
failed: int = 0
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
@dataclass
|
|
125
|
+
class CategoryProgress:
|
|
126
|
+
rows: dict[InspectionCategory, _CategoryRow] = field(default_factory=dict)
|
|
127
|
+
_printed_lines: int = 0
|
|
128
|
+
_started: bool = False
|
|
129
|
+
_interactive: bool = False
|
|
130
|
+
_spinner: _Spinner | None = None
|
|
131
|
+
|
|
132
|
+
@classmethod
|
|
133
|
+
def from_totals(cls, totals: dict[InspectionCategory, int]) -> "CategoryProgress":
|
|
134
|
+
rows = {
|
|
135
|
+
cat: _CategoryRow(category=cat, total=totals.get(cat, 0))
|
|
136
|
+
for cat in _CATEGORY_ORDER
|
|
137
|
+
}
|
|
138
|
+
return cls(rows=rows)
|
|
139
|
+
|
|
140
|
+
def start(self) -> None:
|
|
141
|
+
if self._started:
|
|
142
|
+
return
|
|
143
|
+
self._started = True
|
|
144
|
+
self._interactive = supports_color()
|
|
145
|
+
if not self._interactive:
|
|
146
|
+
return
|
|
147
|
+
total = sum(r.total for r in self.rows.values())
|
|
148
|
+
if total == 0:
|
|
149
|
+
return
|
|
150
|
+
self._spinner = _Spinner(f"Running {total} tests")
|
|
151
|
+
self._spinner.start()
|
|
152
|
+
self._printed_lines = 1
|
|
153
|
+
|
|
154
|
+
def record(self, category: InspectionCategory, passing: bool) -> None:
|
|
155
|
+
row = self.rows.get(category)
|
|
156
|
+
if row is None:
|
|
157
|
+
return
|
|
158
|
+
if row.total == 0:
|
|
159
|
+
row.total = max(1, row.total)
|
|
160
|
+
row.done += 1
|
|
161
|
+
if not passing:
|
|
162
|
+
row.failed += 1
|
|
163
|
+
self._redraw()
|
|
164
|
+
|
|
165
|
+
def finalize(self) -> None:
|
|
166
|
+
if self._spinner is not None:
|
|
167
|
+
self._spinner.stop()
|
|
168
|
+
self._spinner = None
|
|
169
|
+
self._redraw(final=True)
|
|
170
|
+
if self._interactive and self._printed_lines:
|
|
171
|
+
click.echo()
|
|
172
|
+
|
|
173
|
+
def _redraw(self, *, final: bool = False) -> None:
|
|
174
|
+
if not self._started:
|
|
175
|
+
return
|
|
176
|
+
rendered = self._render()
|
|
177
|
+
if not rendered:
|
|
178
|
+
return
|
|
179
|
+
if self._spinner is not None:
|
|
180
|
+
self._spinner.stop()
|
|
181
|
+
self._spinner = None
|
|
182
|
+
if self._interactive and self._printed_lines:
|
|
183
|
+
sys.stdout.write(f"\033[{self._printed_lines}F")
|
|
184
|
+
for line in rendered:
|
|
185
|
+
sys.stdout.write("\033[2K")
|
|
186
|
+
sys.stdout.write(line + "\n")
|
|
187
|
+
sys.stdout.flush()
|
|
188
|
+
self._printed_lines = len(rendered)
|
|
189
|
+
elif self._interactive and not self._printed_lines:
|
|
190
|
+
for line in rendered:
|
|
191
|
+
sys.stdout.write(line + "\n")
|
|
192
|
+
sys.stdout.flush()
|
|
193
|
+
self._printed_lines = len(rendered)
|
|
194
|
+
elif not self._interactive and final:
|
|
195
|
+
for line in rendered:
|
|
196
|
+
click.echo(line)
|
|
197
|
+
|
|
198
|
+
def _render(self) -> list[str]:
|
|
199
|
+
out: list[str] = []
|
|
200
|
+
for cat in _CATEGORY_ORDER:
|
|
201
|
+
row = self.rows[cat]
|
|
202
|
+
if row.total == 0:
|
|
203
|
+
continue
|
|
204
|
+
label = cat.value.upper().ljust(16)
|
|
205
|
+
ratio = row.done / row.total if row.total else 0.0
|
|
206
|
+
filled = int(round(ratio * _BAR_WIDTH))
|
|
207
|
+
bar = _BLOCK_FULL * filled + _BLOCK_EMPTY * (_BAR_WIDTH - filled)
|
|
208
|
+
colored_bar = _truecolor(bar, _CATEGORY_COLORS[cat], bold=True)
|
|
209
|
+
count = f"{row.done}/{row.total}".rjust(7)
|
|
210
|
+
tail = ""
|
|
211
|
+
if row.done >= row.total:
|
|
212
|
+
if row.failed == 0:
|
|
213
|
+
tail = " " + _truecolor("✓", (74, 222, 128))
|
|
214
|
+
else:
|
|
215
|
+
tail = " " + _truecolor(f"✗ {row.failed} failed", (239, 68, 68))
|
|
216
|
+
out.append(f" {label} {colored_bar} {count}{tail}")
|
|
217
|
+
return out
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def category_totals_from_specs(
|
|
221
|
+
specs: Iterable[object],
|
|
222
|
+
) -> dict[InspectionCategory, int]:
|
|
223
|
+
counts: dict[InspectionCategory, int] = {cat: 0 for cat in _CATEGORY_ORDER}
|
|
224
|
+
for spec in specs:
|
|
225
|
+
if not isinstance(spec, InspectionSpec):
|
|
226
|
+
continue
|
|
227
|
+
cat = spec.category
|
|
228
|
+
counts[cat] = counts.get(cat, 0) + 1
|
|
229
|
+
return counts
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
|
|
6
|
+
import click
|
|
7
|
+
|
|
8
|
+
from ifixai.cli._branding import _ACCENT_RGB, _DIM_RGB, _truecolor
|
|
9
|
+
|
|
10
|
+
ENV_NO_PROMPT = "IFIXAI_NO_PROMPT"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def print_imecore_conclusion(*, quiet: bool) -> None:
|
|
14
|
+
if quiet:
|
|
15
|
+
return
|
|
16
|
+
if os.environ.get(ENV_NO_PROMPT):
|
|
17
|
+
return
|
|
18
|
+
if not sys.stdout.isatty():
|
|
19
|
+
_print_plain_conclusion()
|
|
20
|
+
return
|
|
21
|
+
|
|
22
|
+
click.echo()
|
|
23
|
+
click.echo(click.style("Conclusion", bold=True))
|
|
24
|
+
click.echo(
|
|
25
|
+
" The report above isn't a bug list. It's the absence of an alignment layer."
|
|
26
|
+
)
|
|
27
|
+
click.echo()
|
|
28
|
+
click.echo(" " + _truecolor("iFixAi measures it. iMe ends it.", _ACCENT_RGB, bold=True))
|
|
29
|
+
click.echo()
|
|
30
|
+
click.echo(
|
|
31
|
+
" iMe is the deterministic alignment runtime: non-LLM, six constitutional"
|
|
32
|
+
)
|
|
33
|
+
click.echo(" rules, six-stage pipeline.")
|
|
34
|
+
click.echo()
|
|
35
|
+
click.echo(
|
|
36
|
+
" " + _truecolor("Probabilistic guardrails fail. Deterministic rules don't.", _ACCENT_RGB, bold=True)
|
|
37
|
+
)
|
|
38
|
+
click.echo()
|
|
39
|
+
click.echo(" " + _truecolor("Limited release. Selected deployments.", _DIM_RGB))
|
|
40
|
+
click.echo(
|
|
41
|
+
" Request access → " + _truecolor("https://ifixai.ai/ime", _ACCENT_RGB, bold=True)
|
|
42
|
+
)
|
|
43
|
+
click.echo()
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _print_plain_conclusion() -> None:
|
|
47
|
+
click.echo()
|
|
48
|
+
click.echo("Conclusion")
|
|
49
|
+
click.echo(" The report above isn't a bug list. It's the absence of an alignment layer.")
|
|
50
|
+
click.echo()
|
|
51
|
+
click.echo(" iFixAi measures it. iMe ends it.")
|
|
52
|
+
click.echo()
|
|
53
|
+
click.echo(" iMe is the deterministic alignment runtime: non-LLM, six constitutional")
|
|
54
|
+
click.echo(" rules, six-stage pipeline.")
|
|
55
|
+
click.echo()
|
|
56
|
+
click.echo(" Probabilistic guardrails fail. Deterministic rules don't.")
|
|
57
|
+
click.echo()
|
|
58
|
+
click.echo(" Limited release. Selected deployments.")
|
|
59
|
+
click.echo(" Request access → https://ifixai.ai/ime")
|
|
60
|
+
click.echo()
|