Context
Operator-flagged during the #12123 / PR #12642 REM-retention work: ai/services/memory-core/helpers/RemRunStateStore.mjs carries a PascalCase filename, but it is a loose-function-export helper (no class). The Neo naming convention reserves PascalCase filenames for classes (including neo classes — extends core.Base, static config). Loose-function-export helpers, namespace-object modules, and constants modules should use camelCase filenames.
The trigger generalizes: a systematic sweep shows the pattern fails across the ai/ tree (concentrated in helpers/ dirs) and once in buildScripts/.
The Problem
A PascalCase filename falsely signals "this is a class" to both human readers and agents enumerating the tree. The cost compounds: a reader opening ConsumerFrictionHelper.mjs or RemRunStateStore.mjs expects a class with static config + instance methods, then finds a flat list of export functions. The convention exists precisely to make PascalCase ⇒ class a reliable at-a-glance signal.
V-B-A evidence (find ai -name '[A-Z]*.mjs' | xargs grep -L '^…class [A-Z]'):
- 156 PascalCase
.mjs in ai/; 17 have no class declaration.
- Each confirmed loose-fn / namespace-object / constants module via
grep '^export' (e.g. RemRunStateStore.mjs → 0 class declarations, 7 export function).
The Architectural Reality
PascalCase.mjs ⇒ a class file (class X extends Base + Neo.setupClass(X), or a plain export default class). camelCase ⇒ a module of loose exports. The files below all fail the class test. Renaming each requires a repo-wide inbound-import sweep (every import … from '.../RemRunStateStore.mjs' site updated): scoped greps miss .mjs @see / string refs, and synced apps/portal/resources/data + frozen resources/content must be excluded; verify grep for each old basename returns 0.
The Fix
Rename each to camelCase + update all import sites.
ai/ — loose-function helpers (12):
services/memory-core/helpers/RemRunStateStore.mjs → remRunStateStore.mjs
services/memory-core/helpers/ConsumerFrictionHelper.mjs → consumerFrictionHelper.mjs
services/memory-core/helpers/HarnessClassifier.mjs → harnessClassifier.mjs
services/memory-core/helpers/EmbeddingProviderConfig.mjs → embeddingProviderConfig.mjs
services/graph/ProviderReadinessHelper.mjs → providerReadinessHelper.mjs
services/knowledge-base/helpers/KbReconciliationEngine.mjs → kbReconciliationEngine.mjs
services/knowledge-base/helpers/KbGarbageCollectionEngine.mjs → kbGarbageCollectionEngine.mjs
services/knowledge-base/helpers/TenantRepoAccessContract.mjs → tenantRepoAccessContract.mjs
services/knowledge-base/helpers/GitMirror.mjs → gitMirror.mjs
mcp/server/shared/Logger.mjs → logger.mjs
mcp/server/shared/helpers/DeploymentConfig.mjs → deploymentConfig.mjs
mcp/validation/OpenApiValidator.mjs → openApiValidator.mjs (loose functions + an export {} block)
ai/ — constants / namespace-object / re-export modules (5):
daemons/orchestrator/TaskDefinitions.mjs → taskDefinitions.mjs (constants + builders)
graph/queries/Traversal.mjs → traversal.mjs (arrow-fn exports)
services/knowledge-base/helpers/KbAlertRuleEngine.mjs → kbAlertRuleEngine.mjs (constants + rule fns)
services/knowledge-base/helpers/TenantRepoIngestEnvelopeBuilder.mjs → tenantRepoIngestEnvelopeBuilder.mjs (⚠ also rename the const TenantRepoIngestEnvelopeBuilder = {…} default-export identifier — a PascalCase const-object is the same misleading "class-like" signal)
mcp/server/memory-core/helpers/EmbeddingProviderConfig.mjs → embeddingProviderConfig.mjs (re-export — follows the services-layer rename in lockstep)
buildScripts/ (1):
util/Sanitizer.mjs → sanitizer.mjs
Avoided Traps
- Do NOT rename actual class files. PascalCase is correct for
class X extends Base files (DreamService.mjs, SessionService.mjs, SemanticGraphExtractor.mjs, all of src/). The grep filter excludes them by construction.
- Namespace-object default exports: for
TenantRepoIngestEnvelopeBuilder.mjs, rename both the filename AND the identifier.
- Re-exports follow the source: the MC
EmbeddingProviderConfig.mjs only re-exports the services one — rename in lockstep, single PR.
- Case-insensitive-FS hazard: on macOS, a pure case-change rename may need the two-step
git mv X tmp && git mv tmp x to register.
Decision Record impact
None — aligned with the existing Neo naming convention (PascalCase ⇒ class). No ADR introduced or challenged.
Acceptance Criteria
Out of Scope
src/ framework class files (PascalCase correct — they ARE classes).
- Any file containing a class declaration.
- A
test/ sweep — this analysis covered ai/ + buildScripts/; a test/ pass is a reasonable follow-up if it surfaces instances.
Related
- Triggered by #12123 / PR #12642 (RemRunStateStore retention work).
Origin Session ID: 5f3fd8c4-ce8d-4a69-bbfe-336c5eeffdd3
Retrieval Hint: "PascalCase loose-fn helper camelCase rename" / find ai -name '[A-Z]*.mjs' | xargs grep -L class
Live latest-open sweep: checked latest 12 open issues + searched naming/convention/camelCase at 2026-06-06; no equivalent found.
Context
Operator-flagged during the #12123 / PR #12642 REM-retention work:
ai/services/memory-core/helpers/RemRunStateStore.mjscarries a PascalCase filename, but it is a loose-function-export helper (no class). The Neo naming convention reserves PascalCase filenames for classes (including neo classes —extends core.Base,static config). Loose-function-export helpers, namespace-object modules, and constants modules should use camelCase filenames.The trigger generalizes: a systematic sweep shows the pattern fails across the
ai/tree (concentrated inhelpers/dirs) and once inbuildScripts/.The Problem
A PascalCase filename falsely signals "this is a class" to both human readers and agents enumerating the tree. The cost compounds: a reader opening
ConsumerFrictionHelper.mjsorRemRunStateStore.mjsexpects a class withstatic config+ instance methods, then finds a flat list ofexport functions. The convention exists precisely to makePascalCase ⇒ classa reliable at-a-glance signal.V-B-A evidence (
find ai -name '[A-Z]*.mjs' | xargs grep -L '^…class [A-Z]'):.mjsinai/; 17 have no class declaration.grep '^export'(e.g.RemRunStateStore.mjs→ 0 class declarations, 7export function).The Architectural Reality
PascalCase.mjs⇒ a class file (class X extends Base+Neo.setupClass(X), or a plainexport default class). camelCase ⇒ a module of loose exports. The files below all fail the class test. Renaming each requires a repo-wide inbound-import sweep (everyimport … from '.../RemRunStateStore.mjs'site updated): scoped greps miss.mjs@see/ string refs, and syncedapps/portal/resources/data+ frozenresources/contentmust be excluded; verifygrepfor each old basename returns 0.The Fix
Rename each to camelCase + update all import sites.
ai/— loose-function helpers (12):services/memory-core/helpers/RemRunStateStore.mjs→remRunStateStore.mjsservices/memory-core/helpers/ConsumerFrictionHelper.mjs→consumerFrictionHelper.mjsservices/memory-core/helpers/HarnessClassifier.mjs→harnessClassifier.mjsservices/memory-core/helpers/EmbeddingProviderConfig.mjs→embeddingProviderConfig.mjsservices/graph/ProviderReadinessHelper.mjs→providerReadinessHelper.mjsservices/knowledge-base/helpers/KbReconciliationEngine.mjs→kbReconciliationEngine.mjsservices/knowledge-base/helpers/KbGarbageCollectionEngine.mjs→kbGarbageCollectionEngine.mjsservices/knowledge-base/helpers/TenantRepoAccessContract.mjs→tenantRepoAccessContract.mjsservices/knowledge-base/helpers/GitMirror.mjs→gitMirror.mjsmcp/server/shared/Logger.mjs→logger.mjsmcp/server/shared/helpers/DeploymentConfig.mjs→deploymentConfig.mjsmcp/validation/OpenApiValidator.mjs→openApiValidator.mjs(loosefunctions + anexport {}block)ai/— constants / namespace-object / re-export modules (5):daemons/orchestrator/TaskDefinitions.mjs→taskDefinitions.mjs(constants + builders)graph/queries/Traversal.mjs→traversal.mjs(arrow-fn exports)services/knowledge-base/helpers/KbAlertRuleEngine.mjs→kbAlertRuleEngine.mjs(constants + rule fns)services/knowledge-base/helpers/TenantRepoIngestEnvelopeBuilder.mjs→tenantRepoIngestEnvelopeBuilder.mjs(⚠ also rename theconst TenantRepoIngestEnvelopeBuilder = {…}default-export identifier — a PascalCase const-object is the same misleading "class-like" signal)mcp/server/memory-core/helpers/EmbeddingProviderConfig.mjs→embeddingProviderConfig.mjs(re-export — follows the services-layer rename in lockstep)buildScripts/(1):util/Sanitizer.mjs→sanitizer.mjsAvoided Traps
class X extends Basefiles (DreamService.mjs,SessionService.mjs,SemanticGraphExtractor.mjs, all ofsrc/). The grep filter excludes them by construction.TenantRepoIngestEnvelopeBuilder.mjs, rename both the filename AND the identifier.EmbeddingProviderConfig.mjsonly re-exports the services one — rename in lockstep, single PR.git mv X tmp && git mv tmp xto register.Decision Record impact
None — aligned with the existing Neo naming convention (
PascalCase ⇒ class). No ADR introduced or challenged.Acceptance Criteria
@see/ dynamic-import reference updated;grepfor each old basename returns 0 outside excluded synced/frozen dirs.ERR_MODULE_NOT_FOUND.Out of Scope
src/framework class files (PascalCase correct — they ARE classes).test/sweep — this analysis coveredai/+buildScripts/; atest/pass is a reasonable follow-up if it surfaces instances.Related
Origin Session ID: 5f3fd8c4-ce8d-4a69-bbfe-336c5eeffdd3 Retrieval Hint: "PascalCase loose-fn helper camelCase rename" /
find ai -name '[A-Z]*.mjs' | xargs grep -L classLive latest-open sweep: checked latest 12 open issues + searched naming/convention/camelCase at 2026-06-06; no equivalent found.