From 714c1580483ef389810a40c42aada0ad78d70d24 Mon Sep 17 00:00:00 2001 From: lmist Date: Sat, 27 Jun 2026 22:07:48 +0300 Subject: [PATCH] Enforce naming conventions via Perl::Critic Add a focused .perlcriticrc enabling the NamingConventions::Capitalization and ProhibitAmbiguousNames policies so identifier naming is machine-checkable rather than review-by-eye. The config encodes the de-facto standard already used across lib/ (CamelCase packages, snake_case subs/vars with leading _ for private/builder subs, ALL_CAPS constants/globals) and is tuned to pass clean on the current tree (110 files, 0 violations) so it can serve as a CI gate. Document the conventions in README (new 'Coding conventions' section) and CLAUDE.md. Rename the camelCase $operationId/$opId locals in Langertha::Role::OpenAPI to snake_case to match the surrounding style; the OpenAPI spec key and POD term 'operationId' are unchanged (behaviour-preserving). Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> --- .perlcriticrc | 65 +++++++++++++++++++++++++++++++++++ CLAUDE.md | 4 +++ Changes | 10 ++++++ README.md | 28 +++++++++++++++ lib/Langertha/Role/OpenAPI.pm | 22 ++++++------ 5 files changed, 118 insertions(+), 11 deletions(-) create mode 100644 .perlcriticrc diff --git a/.perlcriticrc b/.perlcriticrc new file mode 100644 index 0000000..8cad3aa --- /dev/null +++ b/.perlcriticrc @@ -0,0 +1,65 @@ +# Perl::Critic configuration for Langertha +# +# Scope: this file enforces NAMING CONVENTIONS as machine-checkable rules so +# that contributors (human and AI agents) get consistent identifiers without +# relying on review-by-eye. It deliberately runs only the naming-related +# policies; broader style/complexity policies are left out so the gate stays +# focused and low-noise. It is tuned to pass clean on the current tree, so it +# can be wired into CI as an enforcement gate. +# +# Conventions enforced (the de-facto standard already used across lib/): +# * Packages StartsWithUpper (CamelCase) Langertha::Engine::OpenAIBase +# * Subroutines snake_case from_openai, chat_with_tools_f +# (leading "_" for private/builder subs allowed: +# _build_api_key, _build__json) +# * Lexical variables snake_case my $tool_choice +# * Globals / constants snake_case OR ALL_CAPS our $VERSION, our %ROLE_TO_CAPS +# +# Run: perlcritic lib/ bin/ +# (.perlcriticrc is the default profile lookup path, so no --profile needed) + +# Only report violations from the policies explicitly enabled below, and run +# them regardless of their stock severity. +severity = 1 +only = 1 +verbose = 8 + +#----------------------------------------------------------------------------- +# Capitalization: enforce the project's identifier casing. +# Each option takes a built-in scheme tag (e.g. :starts_with_upper) or a +# literal regexp the identifier must match. The snake_case regexp below allows +# leading underscores (private/builder subs) and multi-underscore separators +# (Moose builders for private attributes, e.g. _build__json -> attr _json). +#----------------------------------------------------------------------------- +[NamingConventions::Capitalization] +severity = 5 + +# Packages start with an upper-case letter (CamelCase namespaces). +# package_exemptions matches each "::"-separated component independently and is +# auto-anchored with \A...\z, so "vLLM" exempts the Langertha::Engine::vLLM +# namespace whose capitalization mirrors the upstream project's own brand. +packages = :starts_with_upper +package_exemptions = vLLM + +# Subroutines: snake_case, optional leading underscore(s) for private helpers +# and Moose builders. subroutine_exemptions REPLACES the policy's built-in list, +# so we re-list the Moose/Perl lifecycle + tie/overload methods and add +# FOREIGNBUILDARGS (MooseX::NonMoose) which Moose dispatches on by exact name. +subroutines = _*[a-z][a-z0-9]*(?:_+[a-z0-9]+)* +subroutine_exemptions = AUTOLOAD BUILD BUILDARGS FOREIGNBUILDARGS CLEAR CLOSE DELETE DEMOLISH DESTROY EXISTS EXTEND FETCH FETCHSIZE FIRSTKEY GETC NEXTKEY POP PRINT PRINTF PUSH READ READLINE SCALAR SHIFT SPLICE STORE STORESIZE TIEARRAY TIEHANDLE TIEHASH TIESCALAR UNSHIFT UNTIE WRITE + +# Local lexicals: snake_case (optional leading underscore allowed). +local_lexical_variables = _*[a-z][a-z0-9]*(?:_+[a-z0-9]+)* + +# Package globals & constants: snake_case OR ALL_CAPS +# (covers our $VERSION, our %ROLE_TO_CAPS, and ordinary package globals). +global_variables = [a-z][a-z0-9]*(?:_[a-z0-9]+)*|[A-Z][A-Z0-9_]* + +#----------------------------------------------------------------------------- +# Reject deliberately confusing identifier names. The forbid list is the PBP +# default minus "last", "record", "set", and "left", which appear here with +# unambiguous, well-scoped meanings (e.g. $last = most recent result). +#----------------------------------------------------------------------------- +[NamingConventions::ProhibitAmbiguousNames] +severity = 5 +forbid = abstract bases close contract no right second diff --git a/CLAUDE.md b/CLAUDE.md index 8098b7a..86074f6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -79,6 +79,10 @@ Test framework: `Test2::Bundle::More`. `dzil release` is forbidden without expli - **MCP**: `Net::Async::MCP` (client), `MCP::Server` (tool definitions, `inputSchema` camelCase). - **POD**: `@Author::GETTY` PodWeaver. `# ABSTRACT:` required on every `.pm`; inline `=attr`, `=method`, `=seealso`. Use the `pod-writer` agent for documentation. +- **Naming**: `CamelCase` packages, `snake_case` subs/methods/variables (leading `_` for + private/builder subs), `ALL_CAPS` for constants/package globals (`$VERSION`, `%ROLE_TO_CAPS`). + Enforced by `.perlcriticrc` (NamingConventions policies) — run `perlcritic lib/ bin/`; the + tree passes clean. See README "Coding conventions". ## Architecture diff --git a/Changes b/Changes index 560e2fa..0e66abe 100644 --- a/Changes +++ b/Changes @@ -2,6 +2,16 @@ Revision history for Langertha {{$NEXT}} + - Enforce naming conventions via Perl::Critic. Added a focused .perlcriticrc + enabling the NamingConventions::Capitalization and ProhibitAmbiguousNames + policies (CamelCase packages, snake_case subs/vars with leading "_" for + private/builder subs, ALL_CAPS constants/globals), tuned to pass clean on + the current tree so it can serve as a CI gate. Documented the conventions + in README ("Coding conventions") and CLAUDE.md. Renamed the camelCase + $operationId/$opId locals in Langertha::Role::OpenAPI to snake_case + ($operation_id) to match the surrounding style (behaviour-preserving; the + OpenAPI spec key and POD term "operationId" are unchanged). + - Tool wire-translation value-object symmetry (karr #1, #2; ADR 0001/0003). Reconciled the two inbound tool-call entry points ADR 0001 flagged as unsettled: Langertha::ToolCall->extract is now strictly the format-pinned diff --git a/README.md b/README.md index 71efd10..290b041 100644 --- a/README.md +++ b/README.md @@ -931,6 +931,34 @@ my $models = $engine->list_models(force_refresh => 1); # Bypass cache Results are cached for 1 hour (configurable via `models_cache_ttl`). +## Coding conventions + +Langertha enforces consistent identifier naming so the codebase stays +predictable for both human contributors and AI agents. The conventions are the +de-facto standard already used throughout `lib/`: + +| Identifier | Convention | Example | +|---|---|---| +| Packages / namespaces | `CamelCase` | `Langertha::Engine::OpenAIBase` | +| Subroutines & methods | `snake_case` | `from_openai`, `chat_with_tools_f` | +| Private / builder subs | leading `_` + `snake_case` | `_build_api_key`, `_build__json` | +| Lexical variables | `snake_case` | `my $tool_choice` | +| Package globals / constants | `snake_case` or `ALL_CAPS` | `our $VERSION`, `our %ROLE_TO_CAPS` | + +These rules are machine-checkable via [Perl::Critic](https://metacpan.org/pod/Perl::Critic). +A focused [`.perlcriticrc`](.perlcriticrc) at the repo root enables the +`NamingConventions::Capitalization` and `NamingConventions::ProhibitAmbiguousNames` +policies (and nothing else, to keep the gate low-noise). The current tree passes +clean, so it can be wired into CI as an enforcement gate. + +```bash +# One-time: install the linter +cpanm Perl::Critic + +# Lint the shipped code (uses .perlcriticrc automatically) +perlcritic lib/ bin/ +``` + ## Testing ```bash diff --git a/lib/Langertha/Role/OpenAPI.pm b/lib/Langertha/Role/OpenAPI.pm index ee19885..592083b 100644 --- a/lib/Langertha/Role/OpenAPI.pm +++ b/lib/Langertha/Role/OpenAPI.pm @@ -35,13 +35,13 @@ sub _build_openapi_operations { for my $method (keys %{$paths->{$path}}) { next unless ref $paths->{$path}{$method} eq 'HASH'; my $op = $paths->{$path}{$method}; - my $opId = $op->{operationId} or next; + my $operation_id = $op->{operationId} or next; my $ct; if ($op->{requestBody} && $op->{requestBody}{content}) { $ct = 'application/json' if $op->{requestBody}{content}{'application/json'}; $ct //= 'multipart/form-data' if $op->{requestBody}{content}{'multipart/form-data'}; } - $operations{$opId} = { + $operations{$operation_id} = { method => uc($method), path => $path, defined $ct ? (content_type => $ct) : (), @@ -105,10 +105,10 @@ a limited compatibility mode. =cut sub can_operation { - my ( $self, $operationId ) = @_; + my ( $self, $operation_id ) = @_; return 1 unless scalar @{$self->supported_operations} > 0; my %so = map { $_, 1 } @{$self->supported_operations}; - return $so{$operationId}; + return $so{$operation_id}; } =method can_operation @@ -121,12 +121,12 @@ returns true when C is empty (unrestricted mode). =cut sub get_operation { - my ( $self, $operationId ) = @_; + my ( $self, $operation_id ) = @_; croak "".(ref $self)." runs in compatibility mode and is unable to perform this OpenAPI operation" - unless ($self->can_operation($operationId)); + unless ($self->can_operation($operation_id)); my $ops = $self->openapi_operations; - my $op = $ops->{operations}{$operationId} - or croak "".(ref $self).": operationId '$operationId' not found in spec"; + my $op = $ops->{operations}{$operation_id} + or croak "".(ref $self).": operationId '$operation_id' not found in spec"; my $url = $self->url || $ops->{server_url}; return ( $op->{method}, $url.$op->{path}, $op->{content_type} ); } @@ -142,9 +142,9 @@ operation is not in C. =cut sub generate_request { - my ( $self, $operationId, $response_call, %args ) = @_; - my ( $method, $url, $content_type ) = $self->get_operation($operationId); - $log->debugf("[%s] %s %s (%s)", ref $self, $method, $url, $operationId); + my ( $self, $operation_id, $response_call, %args ) = @_; + my ( $method, $url, $content_type ) = $self->get_operation($operation_id); + $log->debugf("[%s] %s %s (%s)", ref $self, $method, $url, $operation_id); $args{content_type} = $content_type if defined $content_type; return $self->generate_http_request( $method, $url, $response_call, %args ); }