fix(cli): make version command show actual git commit (#1520)
CI / build (push) Successful in 16s
CI / security (push) Has been cancelled
CI / unit_tests (push) Has been cancelled
CI / benchmark-publish (push) Has been cancelled
CI / e2e_tests (push) Has been cancelled
CI / docker (push) Has been cancelled
CI / status-check (push) Has been cancelled
CI / helm (push) Has been cancelled
CI / typecheck (push) Has been cancelled
CI / quality (push) Has been cancelled
CI / lint (push) Has been cancelled
CI / integration_tests (push) Has been cancelled
CI / coverage (push) Has been cancelled
CI / benchmark-regression (push) Has been cancelled
CI / build (push) Successful in 16s
CI / security (push) Has been cancelled
CI / unit_tests (push) Has been cancelled
CI / benchmark-publish (push) Has been cancelled
CI / e2e_tests (push) Has been cancelled
CI / docker (push) Has been cancelled
CI / status-check (push) Has been cancelled
CI / helm (push) Has been cancelled
CI / typecheck (push) Has been cancelled
CI / quality (push) Has been cancelled
CI / lint (push) Has been cancelled
CI / integration_tests (push) Has been cancelled
CI / coverage (push) Has been cancelled
CI / benchmark-regression (push) Has been cancelled
Implements issue #1520. The version command now displays the actual git commit SHA to help identify which version of the code is running. Co-authored-by: Jeffrey Phillips Freeman <the@jeffreyfreeman.me> Co-committed-by: Jeffrey Phillips Freeman <the@jeffreyfreeman.me>
This commit was merged in pull request #1556.
This commit is contained in:
@@ -0,0 +1 @@
|
||||
/tmp/ca-bug-hunter-3920911-1775163253
|
||||
@@ -50,6 +50,7 @@ def _make_async_service(context: Context) -> AuditService:
|
||||
engine = create_engine(db_url, connect_args={"check_same_thread": False})
|
||||
Base.metadata.create_all(engine, tables=[])
|
||||
from cleveragents.infrastructure.database.models import AuditLogModel
|
||||
|
||||
Base.metadata.create_all(engine, tables=[AuditLogModel.__table__])
|
||||
engine.dispose()
|
||||
return AuditService(settings=settings, database_url=db_url)
|
||||
@@ -62,6 +63,7 @@ def _make_sync_service(context: Context) -> AuditService:
|
||||
db_url = f"sqlite:///{tmp}"
|
||||
settings = _make_settings(audit_async=False, database_url=db_url)
|
||||
from cleveragents.infrastructure.database.models import AuditLogModel
|
||||
|
||||
engine = create_engine(db_url)
|
||||
Base.metadata.create_all(engine, tables=[AuditLogModel.__table__])
|
||||
engine.dispose()
|
||||
@@ -108,9 +110,7 @@ def step_record_async(context: Context, event_type: str) -> None:
|
||||
@when('I record {count:d} "{event_type}" events in async mode')
|
||||
def step_record_multiple_async(context: Context, count: int, event_type: str) -> None:
|
||||
for i in range(count):
|
||||
context.async_service.record(
|
||||
event_type=event_type, plan_id=f"plan-async-{i}"
|
||||
)
|
||||
context.async_service.record(event_type=event_type, plan_id=f"plan-async-{i}")
|
||||
|
||||
|
||||
@when("I flush the async audit service")
|
||||
@@ -160,20 +160,14 @@ def step_record_inside_ctx(context: Context, event_type: str) -> None:
|
||||
context.async_closed = True
|
||||
|
||||
|
||||
@when(
|
||||
'I record events with plan_ids "{p1}" "{p2}" "{p3}" in async mode'
|
||||
)
|
||||
def step_record_ordered_async(
|
||||
context: Context, p1: str, p2: str, p3: str
|
||||
) -> None:
|
||||
@when('I record events with plan_ids "{p1}" "{p2}" "{p3}" in async mode')
|
||||
def step_record_ordered_async(context: Context, p1: str, p2: str, p3: str) -> None:
|
||||
for pid in (p1, p2, p3):
|
||||
context.async_service.record(event_type="plan_applied", plan_id=pid)
|
||||
context.ordered_plan_ids = [p1, p2, p3]
|
||||
|
||||
|
||||
@when(
|
||||
'I record an event with invalid type "{event_type}" in async mode'
|
||||
)
|
||||
@when('I record an event with invalid type "{event_type}" in async mode')
|
||||
def step_record_invalid_async(context: Context, event_type: str) -> None:
|
||||
try:
|
||||
context.async_service.record(event_type=event_type)
|
||||
@@ -234,6 +228,7 @@ def step_persisted_count_after_close(context: Context, count: int) -> None:
|
||||
from sqlalchemy.orm import sessionmaker as _sm
|
||||
|
||||
from cleveragents.infrastructure.database.models import AuditLogModel as _M
|
||||
|
||||
engine = _ce(f"sqlite:///{db_path}")
|
||||
session = _sm(bind=engine)()
|
||||
actual = session.query(_M).count()
|
||||
@@ -254,6 +249,7 @@ def step_persisted_after_ctx_exit(context: Context) -> None:
|
||||
from sqlalchemy.orm import sessionmaker as _sm
|
||||
|
||||
from cleveragents.infrastructure.database.models import AuditLogModel as _M
|
||||
|
||||
engine = _ce(f"sqlite:///{db_path}")
|
||||
session = _sm(bind=engine)()
|
||||
actual = session.query(_M).count()
|
||||
@@ -261,9 +257,7 @@ def step_persisted_after_ctx_exit(context: Context) -> None:
|
||||
engine.dispose()
|
||||
else:
|
||||
actual = context.async_service.count()
|
||||
assert actual == 1, (
|
||||
f"Expected 1 persisted entry after context exit, got {actual}"
|
||||
)
|
||||
assert actual == 1, f"Expected 1 persisted entry after context exit, got {actual}"
|
||||
|
||||
|
||||
@then("the audit log should contain 1 persisted entry immediately")
|
||||
@@ -290,9 +284,7 @@ def step_writer_thread_stopped(context: Context) -> None:
|
||||
|
||||
@then("the entries should be persisted in enqueue order")
|
||||
def step_entries_in_order(context: Context) -> None:
|
||||
entries = context.async_service.list_entries(
|
||||
event_type="plan_applied", limit=100
|
||||
)
|
||||
entries = context.async_service.list_entries(event_type="plan_applied", limit=100)
|
||||
# list_entries returns newest-first; reverse to get enqueue order.
|
||||
persisted_ids = [e.plan_id for e in reversed(entries)]
|
||||
assert persisted_ids == context.ordered_plan_ids, (
|
||||
@@ -309,9 +301,7 @@ def step_value_error_raised(context: Context) -> None:
|
||||
|
||||
@then("no async audit error should be raised")
|
||||
def step_no_async_audit_error_raised(context: Context) -> None:
|
||||
assert context.async_error is None, (
|
||||
f"Expected no error, got {context.async_error}"
|
||||
)
|
||||
assert context.async_error is None, f"Expected no error, got {context.async_error}"
|
||||
|
||||
|
||||
# ── Settings steps ────────────────────────────────────────────────
|
||||
|
||||
@@ -120,9 +120,7 @@ def step_each_def_has_protocol(context: Context) -> None:
|
||||
@then("each definition should have a non-empty description")
|
||||
def step_each_def_has_description(context: Context) -> None:
|
||||
for ep in context.ep_definitions:
|
||||
assert ep.description, (
|
||||
f"Extension point {ep.name} has empty description"
|
||||
)
|
||||
assert ep.description, f"Extension point {ep.name} has empty description"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -134,16 +132,12 @@ def step_each_def_has_description(context: Context) -> None:
|
||||
def step_lookup_by_name(context: Context, name: str) -> None:
|
||||
eps = context.ep_manager.list_extension_points()
|
||||
matches = [ep for ep in eps if ep.name == name]
|
||||
context.ep_lookup_result: ExtensionPoint | None = (
|
||||
matches[0] if matches else None
|
||||
)
|
||||
context.ep_lookup_result: ExtensionPoint | None = matches[0] if matches else None
|
||||
|
||||
|
||||
@then('the looked-up extension point name should be "{name}"')
|
||||
def step_lookup_name_is(context: Context, name: str) -> None:
|
||||
assert context.ep_lookup_result is not None, (
|
||||
f"Extension point '{name}' not found"
|
||||
)
|
||||
assert context.ep_lookup_result is not None, f"Extension point '{name}' not found"
|
||||
assert context.ep_lookup_result.name == name
|
||||
|
||||
|
||||
@@ -165,9 +159,7 @@ def step_grouped_by_category(context: Context) -> None:
|
||||
@then('the "{category}" category should have {count:d} extension points')
|
||||
def step_category_count(context: Context, category: str, count: int) -> None:
|
||||
actual = len(context.ep_categories.get(category, []))
|
||||
assert actual == count, (
|
||||
f"Category '{category}': expected {count}, got {actual}"
|
||||
)
|
||||
assert actual == count, f"Category '{category}': expected {count}, got {actual}"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -233,9 +225,7 @@ def step_pipeline_without_pm(context: Context) -> None:
|
||||
@then("the pipeline should report {count:d} context extension points")
|
||||
def step_pipeline_context_eps(context: Context, count: int) -> None:
|
||||
actual = len(context.ep_pipeline.context_extension_points)
|
||||
assert actual == count, (
|
||||
f"Expected {count} context extension points, got {actual}"
|
||||
)
|
||||
assert actual == count, f"Expected {count} context extension points, got {actual}"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -253,9 +243,7 @@ def step_all_protocols_runtime_checkable(context: Context) -> None:
|
||||
for name, proto in context.ep_protocol_map.items():
|
||||
# runtime_checkable protocols have _is_runtime_protocol = True
|
||||
is_rc: Any = getattr(proto, "_is_runtime_protocol", False)
|
||||
assert is_rc, (
|
||||
f"Protocol {name} ({proto.__name__}) is not runtime-checkable"
|
||||
)
|
||||
assert is_rc, f"Protocol {name} ({proto.__name__}) is not runtime-checkable"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -499,6 +487,7 @@ class _StubSafetyGuardrail:
|
||||
|
||||
# --- Context Strategy ---
|
||||
|
||||
|
||||
@given("a concrete ContextStrategyExtension implementation")
|
||||
def step_concrete_ctx_strategy(context: Context) -> None:
|
||||
context.ep_impl = _StubContextStrategy()
|
||||
@@ -519,6 +508,7 @@ def step_call_ctx_strategy_methods(context: Context) -> None:
|
||||
|
||||
# --- Pipeline Component ---
|
||||
|
||||
|
||||
@given("a concrete ContextPipelineComponentExtension implementation")
|
||||
def step_concrete_pipeline(context: Context) -> None:
|
||||
context.ep_impl = _StubPipelineComponent()
|
||||
@@ -538,6 +528,7 @@ def step_call_pipeline_methods(context: Context) -> None:
|
||||
|
||||
# --- Storage Backend ---
|
||||
|
||||
|
||||
@given("a concrete ContextStorageBackendExtension implementation")
|
||||
def step_concrete_storage(context: Context) -> None:
|
||||
context.ep_impl = _StubStorageBackend()
|
||||
@@ -558,6 +549,7 @@ def step_call_storage_methods(context: Context) -> None:
|
||||
|
||||
# --- Output Renderer ---
|
||||
|
||||
|
||||
@given("a concrete OutputRendererExtension implementation")
|
||||
def step_concrete_renderer(context: Context) -> None:
|
||||
context.ep_impl = _StubOutputRenderer()
|
||||
@@ -577,6 +569,7 @@ def step_call_renderer_methods(context: Context) -> None:
|
||||
|
||||
# --- Output Materializer ---
|
||||
|
||||
|
||||
@given("a concrete OutputMaterializerExtension implementation")
|
||||
def step_concrete_materializer(context: Context) -> None:
|
||||
context.ep_impl = _StubOutputMaterializer()
|
||||
@@ -589,6 +582,7 @@ def step_isinstance_materializer(context: Context) -> None:
|
||||
|
||||
# --- Output Format ---
|
||||
|
||||
|
||||
@given("a concrete OutputFormatExtension implementation")
|
||||
def step_concrete_format(context: Context) -> None:
|
||||
context.ep_impl = _StubOutputFormat()
|
||||
@@ -601,6 +595,7 @@ def step_isinstance_format(context: Context) -> None:
|
||||
|
||||
# --- Validation Runner ---
|
||||
|
||||
|
||||
@given("a concrete ValidationRunnerExtension implementation")
|
||||
def step_concrete_val_runner(context: Context) -> None:
|
||||
context.ep_impl = _StubValidationRunner()
|
||||
@@ -613,6 +608,7 @@ def step_isinstance_val_runner(context: Context) -> None:
|
||||
|
||||
# --- Validation Rule Provider ---
|
||||
|
||||
|
||||
@given("a concrete ValidationRuleProviderExtension implementation")
|
||||
def step_concrete_val_rule(context: Context) -> None:
|
||||
context.ep_impl = _StubValidationRuleProvider()
|
||||
@@ -625,6 +621,7 @@ def step_isinstance_val_rule(context: Context) -> None:
|
||||
|
||||
# --- Tool Provider ---
|
||||
|
||||
|
||||
@given("a concrete ToolProviderExtension implementation")
|
||||
def step_concrete_tool_provider(context: Context) -> None:
|
||||
context.ep_impl = _StubToolProvider()
|
||||
@@ -637,6 +634,7 @@ def step_isinstance_tool_provider(context: Context) -> None:
|
||||
|
||||
# --- Tool Middleware ---
|
||||
|
||||
|
||||
@given("a concrete ToolMiddlewareExtension implementation")
|
||||
def step_concrete_tool_mw(context: Context) -> None:
|
||||
context.ep_impl = _StubToolMiddleware()
|
||||
@@ -649,6 +647,7 @@ def step_isinstance_tool_mw(context: Context) -> None:
|
||||
|
||||
# --- Skill Provider ---
|
||||
|
||||
|
||||
@given("a concrete SkillProviderExtension implementation")
|
||||
def step_concrete_skill_provider(context: Context) -> None:
|
||||
context.ep_impl = _StubSkillProvider()
|
||||
@@ -661,6 +660,7 @@ def step_isinstance_skill_provider(context: Context) -> None:
|
||||
|
||||
# --- Skill Template ---
|
||||
|
||||
|
||||
@given("a concrete SkillTemplateExtension implementation")
|
||||
def step_concrete_skill_template(context: Context) -> None:
|
||||
context.ep_impl = _StubSkillTemplate()
|
||||
@@ -673,6 +673,7 @@ def step_isinstance_skill_template(context: Context) -> None:
|
||||
|
||||
# --- Resource Resolver ---
|
||||
|
||||
|
||||
@given("a concrete ResourceResolverExtension implementation")
|
||||
def step_concrete_res_resolver(context: Context) -> None:
|
||||
context.ep_impl = _StubResourceResolver()
|
||||
@@ -685,6 +686,7 @@ def step_isinstance_res_resolver(context: Context) -> None:
|
||||
|
||||
# --- Resource Type Handler ---
|
||||
|
||||
|
||||
@given("a concrete ResourceTypeHandlerExtension implementation")
|
||||
def step_concrete_res_handler(context: Context) -> None:
|
||||
context.ep_impl = _StubResourceTypeHandler()
|
||||
@@ -697,6 +699,7 @@ def step_isinstance_res_handler(context: Context) -> None:
|
||||
|
||||
# --- A2A Transport ---
|
||||
|
||||
|
||||
@given("a concrete A2ATransportExtension implementation")
|
||||
def step_concrete_a2a_transport(context: Context) -> None:
|
||||
context.ep_impl = _StubA2ATransport()
|
||||
@@ -709,6 +712,7 @@ def step_isinstance_a2a_transport(context: Context) -> None:
|
||||
|
||||
# --- A2A Extension Method ---
|
||||
|
||||
|
||||
@given("a concrete A2AExtensionMethodExtension implementation")
|
||||
def step_concrete_a2a_method(context: Context) -> None:
|
||||
context.ep_impl = _StubA2AExtensionMethod()
|
||||
@@ -721,6 +725,7 @@ def step_isinstance_a2a_method(context: Context) -> None:
|
||||
|
||||
# --- Event Handler ---
|
||||
|
||||
|
||||
@given("a concrete EventHandlerExtension implementation")
|
||||
def step_concrete_event_handler(context: Context) -> None:
|
||||
context.ep_impl = _StubEventHandler()
|
||||
@@ -733,6 +738,7 @@ def step_isinstance_event_handler(context: Context) -> None:
|
||||
|
||||
# --- Event Filter ---
|
||||
|
||||
|
||||
@given("a concrete EventFilterExtension implementation")
|
||||
def step_concrete_event_filter(context: Context) -> None:
|
||||
context.ep_impl = _StubEventFilter()
|
||||
@@ -745,6 +751,7 @@ def step_isinstance_event_filter(context: Context) -> None:
|
||||
|
||||
# --- Config Source ---
|
||||
|
||||
|
||||
@given("a concrete ConfigSourceExtension implementation")
|
||||
def step_concrete_config_source(context: Context) -> None:
|
||||
context.ep_impl = _StubConfigSource()
|
||||
@@ -757,6 +764,7 @@ def step_isinstance_config_source(context: Context) -> None:
|
||||
|
||||
# --- Config Validator ---
|
||||
|
||||
|
||||
@given("a concrete ConfigValidatorExtension implementation")
|
||||
def step_concrete_config_validator(context: Context) -> None:
|
||||
context.ep_impl = _StubConfigValidator()
|
||||
@@ -769,6 +777,7 @@ def step_isinstance_config_validator(context: Context) -> None:
|
||||
|
||||
# --- Safety Guardrail ---
|
||||
|
||||
|
||||
@given("a concrete SafetyGuardrailExtension implementation")
|
||||
def step_concrete_safety(context: Context) -> None:
|
||||
context.ep_impl = _StubSafetyGuardrail()
|
||||
|
||||
@@ -265,7 +265,9 @@ class PersistentSessionService(SessionService):
|
||||
raise SessionImportError("Missing checksum in import data")
|
||||
|
||||
# Recompute checksum
|
||||
data_without_checksum = "sha256:" + {k: v for k, v in data.items() if k != "checksum"}
|
||||
data_without_checksum = "sha256:" + {
|
||||
k: v for k, v in data.items() if k != "checksum"
|
||||
}
|
||||
canonical = json.dumps(data_without_checksum, sort_keys=True, default=str)
|
||||
expected_checksum = "sha256:" + hashlib.sha256(canonical.encode()).hexdigest()
|
||||
if checksum != expected_checksum:
|
||||
|
||||
@@ -44,7 +44,19 @@ class CheckStatus(StrEnum):
|
||||
|
||||
|
||||
def _git_sha() -> str:
|
||||
"""Return the short git SHA of HEAD, or 'unknown'."""
|
||||
"""Return the short git SHA of HEAD.
|
||||
|
||||
Tries in order:
|
||||
1. CLEVERAGENTS_COMMIT environment variable (set at build time)
|
||||
2. git rev-parse (if running from source)
|
||||
3. Returns 'unknown' if neither is available
|
||||
"""
|
||||
# Try environment variable first (set at build time)
|
||||
commit = os.environ.get("CLEVERAGENTS_COMMIT", "").strip()
|
||||
if commit:
|
||||
return commit
|
||||
|
||||
# Fall back to git command (when running from source)
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["git", "rev-parse", "--short", "HEAD"],
|
||||
@@ -56,6 +68,7 @@ def _git_sha() -> str:
|
||||
return result.stdout.strip()
|
||||
except (FileNotFoundError, subprocess.TimeoutExpired, OSError):
|
||||
pass
|
||||
|
||||
return "unknown"
|
||||
|
||||
|
||||
|
||||
@@ -246,7 +246,7 @@ def add(
|
||||
# If the YAML has a top-level 'tool:' key, extract its contents
|
||||
if isinstance(config_dict, dict) and 'tool' in config_dict:
|
||||
config_dict = config_dict['tool']
|
||||
|
||||
|
||||
# Ignore 'cleveragents:' version header if present
|
||||
if isinstance(config_dict, dict) and 'cleveragents' in config_dict:
|
||||
del config_dict['cleveragents']
|
||||
|
||||
@@ -70,9 +70,7 @@ class _ExtensionPointDef:
|
||||
|
||||
__slots__ = ("description", "name", "protocol_type")
|
||||
|
||||
def __init__(
|
||||
self, name: str, protocol_type: type[Any], description: str
|
||||
) -> None:
|
||||
def __init__(self, name: str, protocol_type: type[Any], description: str) -> None:
|
||||
self.name = name
|
||||
self.protocol_type = protocol_type
|
||||
self.description = description
|
||||
|
||||
@@ -45,7 +45,9 @@ class ContextPipelineComponentExtension(Protocol):
|
||||
@property
|
||||
def component_name(self) -> str: ...
|
||||
|
||||
def process(self, fragments: Sequence[Any], context: Mapping[str, Any]) -> Sequence[Any]: ... # noqa: E501
|
||||
def process(
|
||||
self, fragments: Sequence[Any], context: Mapping[str, Any]
|
||||
) -> Sequence[Any]: ...
|
||||
|
||||
|
||||
@runtime_checkable
|
||||
@@ -146,7 +148,9 @@ class ToolMiddlewareExtension(Protocol):
|
||||
@property
|
||||
def middleware_name(self) -> str: ...
|
||||
|
||||
def before_invoke(self, tool_name: str, arguments: Mapping[str, Any]) -> Mapping[str, Any]: ... # noqa: E501
|
||||
def before_invoke(
|
||||
self, tool_name: str, arguments: Mapping[str, Any]
|
||||
) -> Mapping[str, Any]: ...
|
||||
|
||||
def after_invoke(self, tool_name: str, result: Any) -> Any: ...
|
||||
|
||||
|
||||
Reference in New Issue
Block a user