Skip to content

refactor(namespace): centralize all namespace strings and add ESLint enforcement#63

Merged
pipewrk merged 2 commits intomainfrom
refactor/centralize-namespace-constants
Oct 5, 2025
Merged

refactor(namespace): centralize all namespace strings and add ESLint enforcement#63
pipewrk merged 2 commits intomainfrom
refactor/centralize-namespace-constants

Conversation

@pipewrk
Copy link
Contributor

@pipewrk pipewrk commented Oct 5, 2025

Summary

This PR eliminates ALL hardcoded namespace and event name strings outside the namespace module, providing a single source of truth and preventing namespace drift. It also adds an ESLint rule to prevent future violations.

Motivation

After Sprint 4.5 merge, a comprehensive framework-wide audit revealed hardcoded namespace strings scattered across multiple modules:

  1. Public API events - Hardcoded wpk.action.*, wpk.resource.*, wpk.cache.* strings
  2. Reporter namespaces - Inconsistent kernel.policy vs wpk.policy
  3. Namespace detection - Hardcoded 'wpk/' prefix
  4. Infrastructure - Hardcoded BroadcastChannel names and message types

Problems with Hardcoded Strings

  • ❌ Namespace drift risk when changing conventions
  • ❌ Difficult refactoring (must search/replace strings across codebase)
  • ❌ Unclear public API contract
  • ❌ Inconsistent naming between modules
  • ❌ Risk of typos in event names
  • ❌ No IDE autocomplete for event names

Changes

1. New Constants Added

WPK_EVENTS - Public API event names:

export const WPK_EVENTS = {
  ACTION_START: 'wpk.action.start',
  ACTION_COMPLETE: 'wpk.action.complete',
  ACTION_ERROR: 'wpk.action.error',
  RESOURCE_REQUEST: 'wpk.resource.request',
  RESOURCE_RESPONSE: 'wpk.resource.response',
  RESOURCE_ERROR: 'wpk.resource.error',
  CACHE_INVALIDATED: 'wpk.cache.invalidated',
} as const;

WPK_INFRASTRUCTURE - Added BroadcastChannel message types:

ACTIONS_MESSAGE_TYPE_LIFECYCLE: 'wpk.action.lifecycle',
ACTIONS_MESSAGE_TYPE_EVENT: 'wpk.action.event',

2. Files Updated

Namespace Constants (packages/kernel/src/namespace/constants.ts)

  • Added WPK_EVENTS constant with all 7 public API event names
  • Added WPK_INFRASTRUCTURE.ACTIONS_MESSAGE_TYPE_LIFECYCLE
  • Added WPK_INFRASTRUCTURE.ACTIONS_MESSAGE_TYPE_EVENT
  • Exported WPKEvent type for type safety

Action Context (packages/kernel/src/actions/context.ts)

  • Replaced template literals with WPK_EVENTS.ACTION_START/COMPLETE/ERROR
  • Replaced hardcoded 'wpk.actions' with WPK_INFRASTRUCTURE.ACTIONS_CHANNEL
  • Replaced hardcoded message types with WPK_INFRASTRUCTURE.ACTIONS_MESSAGE_TYPE_*
  • Converted nested ternaries to if-else blocks (ESLint compliance)

HTTP Transport (packages/kernel/src/http/fetch.ts)

  • Replaced 'wpk.resource.request/response/error' with WPK_EVENTS.RESOURCE_*

Events Plugin (packages/kernel/src/data/plugins/events.ts)

  • Replaced 'wpk.action.error' with WPK_EVENTS.ACTION_ERROR

Cache Invalidation (packages/kernel/src/resource/cache.ts)

  • Replaced 'wpk.cache.invalidated' with WPK_EVENTS.CACHE_INVALIDATED

Namespace Detection (packages/kernel/src/namespace/detect.ts)

  • Replaced hardcoded 'wpk/' with dynamic ${WPK_NAMESPACE}/

Policy Context (packages/kernel/src/policy/context.ts)

  • Replaced 'kernel.policy' with WPK_SUBSYSTEM_NAMESPACES.POLICY

Tests (packages/kernel/src/actions/__tests__/context.test.ts)

  • Updated expectations from [kernel.policy] to [wpk.policy]

3. ESLint Rule Added

New Rule: @kernel/no-hardcoded-namespace-strings

Detects and prevents hardcoded namespace strings:

// ❌ BAD - Will error at lint time
hooks.doAction('wpk.action.start', event);
const ns = 'wpk.policy';
if (moduleId.startsWith('wpk/')) { }

// ✅ GOOD
import { WPK_EVENTS, WPK_SUBSYSTEM_NAMESPACES, WPK_NAMESPACE } from '../namespace/constants';
hooks.doAction(WPK_EVENTS.ACTION_START, event);
const ns = WPK_SUBSYSTEM_NAMESPACES.POLICY;
if (moduleId.startsWith(`${WPK_NAMESPACE}/`)) { }

Rule Features:

  • Detects all event names, subsystem namespaces, infrastructure IDs
  • Provides specific suggestions for each violation
  • Smart skipping: constants file, ESLint rules, tests, markdown, comments
  • Integrated into pre-commit hooks

Documentation: Comprehensive README at eslint-rules/README.md

Breaking Changes

Reporter Namespace Change

BREAKING: Reporter namespace changed from kernel.policy to wpk.policy

Impact: External code listening to policy events must update event listeners.

Migration:

// Before
wp.hooks.addAction('kernel.policy', handler);

// After  
import { WPK_SUBSYSTEM_NAMESPACES } from '@geekist/wp-kernel';
wp.hooks.addAction(WPK_SUBSYSTEM_NAMESPACES.POLICY, handler);
// or
wp.hooks.addAction('wpk.policy', handler);

Verification

Search Results (Zero Hardcoded Strings)

# No hardcoded 'kernel.' strings in production
rg "kernel\." --type ts --glob '!**/__tests__/**' --glob '!**/*.spec.ts' --glob '!**/*.test.ts' --glob '!**/e2e-utils/**'
# Exit code: 1 (no matches) ✅

# Only doc comments remain with 'wpk/' examples  
rg "wpk/" --type ts --glob '!**/__tests__/**' --glob '!**/*.spec.ts' --glob '!**/*.test.ts'
# Only JSDoc examples showing API usage patterns ✅

# Only doc comments remain with 'wpk.' event names
rg "wpk\." --type ts --glob '!**/__tests__/**' --glob '!**/*.spec.ts' --glob '!**/*.test.ts'
# Only documentation explaining event names ✅

Test Results

✅ All 890 tests passing
✅ Coverage maintained: 96.27% statements, 89.5% branches, 98.91% functions
✅ TypeScript: All checks pass (source + tests)
✅ ESLint: No errors (new rule active)
✅ Documentation builds successfully
✅ Pre-commit hooks pass

ESLint Rule Testing

Created test file with violations - rule correctly detected all 3 patterns:

error  Hardcoded namespace string "wpk.action.start" found. 
       Use WPK_EVENTS.ACTION_START from namespace/constants.ts instead.

error  Hardcoded namespace string "wpk.policy" found. 
       Use WPK_SUBSYSTEM_NAMESPACES.POLICY from namespace/constants.ts instead.

error  Hardcoded namespace string "wpk/" found. 
       Use `${WPK_NAMESPACE}/` or WPK_NAMESPACE constant from namespace/constants.ts instead.

Benefits

  1. Single Source of Truth - All namespace strings defined in namespace/constants.ts
  2. TypeScript Autocomplete - IDE suggests available constants
  3. Easy Refactoring - Change once in constants.ts, affects everywhere
  4. Clear Public API - WPK_EVENTS documents official event names
  5. Prevents Typos - No more 'wpk.acton.start' vs 'wpk.action.start'
  6. Better Documentation - Constants serve as API documentation
  7. Lint-time Prevention - Catches violations before commit

Related Issues

Follow-up from Sprint 4.5 namespace drift discovery during PR #62 review.

Files Changed

Added (2 files, +525 lines):

  • eslint-rules/no-hardcoded-namespace-strings.js - ESLint rule implementation
  • eslint-rules/README.md - Comprehensive documentation

Modified (11 files):

  • packages/kernel/src/namespace/constants.ts - Added WPK_EVENTS + infrastructure constants
  • packages/kernel/src/namespace/index.ts - Export WPK_EVENTS
  • packages/kernel/src/actions/context.ts - Use constants for events + message types
  • packages/kernel/src/http/fetch.ts - Use WPK_EVENTS for resource events
  • packages/kernel/src/data/plugins/events.ts - Use WPK_EVENTS.ACTION_ERROR
  • packages/kernel/src/resource/cache.ts - Use WPK_EVENTS.CACHE_INVALIDATED
  • packages/kernel/src/namespace/detect.ts - Use WPK_NAMESPACE constant
  • packages/kernel/src/policy/context.ts - Use WPK_SUBSYSTEM_NAMESPACES.POLICY
  • packages/kernel/src/actions/__tests__/context.test.ts - Update test expectations
  • eslint.config.js - Enable new ESLint rule
  • docs/api/generated/http/functions/fetch.md - Auto-generated doc update

Checklist

  • All tests pass (890 passing)
  • TypeScript checks pass (source + tests)
  • ESLint checks pass (new rule active and passing)
  • Coverage maintained (≥96% statements, ≥98% functions)
  • Documentation builds successfully
  • Pre-commit hooks pass
  • No hardcoded namespace strings remain (verified with grep)
  • ESLint rule tested and working
  • Breaking changes documented
  • Migration guide provided

… and constants

BREAKING CHANGE: Reporter namespace changed from 'kernel.policy' to 'wpk.policy'

- Add WPK_EVENTS constant with all public API event names:
  - Action lifecycle: wpk.action.start/complete/error
  - Resource transport: wpk.resource.request/response/error
  - Cache invalidation: wpk.cache.invalidated
- Add WPK_INFRASTRUCTURE.ACTIONS_CHANNEL for BroadcastChannel name
- Update all event emission to use WPK_EVENTS constants instead of hardcoded strings
- Update namespace detection to use WPK_NAMESPACE constant instead of 'wpk/' hardcoded
- Update policy reporter to use WPK_SUBSYSTEM_NAMESPACES.POLICY

This eliminates ALL hardcoded namespace/event strings outside the namespace module,
providing a single source of truth and preventing namespace drift.

Files updated:
- packages/kernel/src/namespace/constants.ts - Added WPK_EVENTS constant
- packages/kernel/src/namespace/index.ts - Export WPK_EVENTS and WPKEvent type
- packages/kernel/src/actions/context.ts - Use WPK_EVENTS for lifecycle events
- packages/kernel/src/http/fetch.ts - Use WPK_EVENTS for resource events
- packages/kernel/src/data/plugins/events.ts - Use WPK_EVENTS.ACTION_ERROR
- packages/kernel/src/resource/cache.ts - Use WPK_EVENTS.CACHE_INVALIDATED
- packages/kernel/src/namespace/detect.ts - Use WPK_NAMESPACE constant
- packages/kernel/src/policy/context.ts - Use WPK_SUBSYSTEM_NAMESPACES.POLICY
- packages/kernel/src/actions/__tests__/context.test.ts - Update test expectations

Coverage: All 890 tests passing, no regression
Add comprehensive ESLint rule 'no-hardcoded-namespace-strings' that enforces
usage of centralized namespace constants instead of hardcoded strings.

**Rule Capabilities:**
- Detects hardcoded event names (wpk.action.*, wpk.resource.*, wpk.cache.*)
- Detects subsystem namespaces (wpk.policy, kernel.policy, etc.)
- Detects infrastructure identifiers (BroadcastChannel names, message types)
- Detects namespace prefixes in string operations (wpk/, wpk., kernel.)
- Provides specific suggestions for each violation (WPK_EVENTS.*, WPK_SUBSYSTEM_NAMESPACES.*, etc.)

**Smart Skipping:**
- Skips namespace/constants.ts (where constants are defined)
- Skips eslint-rules/ (where patterns are defined)
- Skips test files (they verify actual string values)
- Skips markdown files (documentation shows actual names)
- Skips comments and JSDoc (documentation references)

**Additional Changes:**
- Add WPK_INFRASTRUCTURE.ACTIONS_MESSAGE_TYPE_LIFECYCLE constant
- Add WPK_INFRASTRUCTURE.ACTIONS_MESSAGE_TYPE_EVENT constant
- Update actions/context.ts to use new message type constants
- Add comprehensive README.md documenting the rule

This prevents namespace drift and ensures a single source of truth for all
framework namespace identifiers, catching violations at lint time rather than
code review.

Files:
- eslint-rules/no-hardcoded-namespace-strings.js (new rule)
- eslint-rules/README.md (documentation)
- eslint.config.js (enable rule)
- packages/kernel/src/namespace/constants.ts (add message type constants)
- packages/kernel/src/actions/context.ts (use new constants)
Copilot AI review requested due to automatic review settings October 5, 2025 14:21
@pipewrk pipewrk added the codex label Oct 5, 2025
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR centralizes all namespace strings into constants in namespace/constants.ts and adds ESLint enforcement to prevent future hardcoded namespace usage. This eliminates namespace drift risk discovered during Sprint 4.5 audit and provides a single source of truth for all framework identifiers.

  • Eliminated ALL hardcoded namespace strings ('wpk.action.start', 'kernel.policy', etc.) across the codebase
  • Added comprehensive WPK_EVENTS constants for public API event names
  • Added ESLint rule with intelligent pattern detection and specific suggestions

Reviewed Changes

Copilot reviewed 12 out of 13 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
packages/kernel/src/namespace/constants.ts Added WPK_EVENTS and infrastructure message type constants
packages/kernel/src/namespace/index.ts Export WPK_EVENTS for public API
packages/kernel/src/actions/context.ts Replace hardcoded event names and message types with constants
packages/kernel/src/http/fetch.ts Use WPK_EVENTS for resource lifecycle events
packages/kernel/src/data/plugins/events.ts Use WPK_EVENTS.ACTION_ERROR constant
packages/kernel/src/resource/cache.ts Use WPK_EVENTS.CACHE_INVALIDATED constant
packages/kernel/src/namespace/detect.ts Use dynamic WPK_NAMESPACE for prefix detection
packages/kernel/src/policy/context.ts Use WPK_SUBSYSTEM_NAMESPACES.POLICY (breaking change from kernel.policy)
packages/kernel/src/actions/__tests__/context.test.ts Update test expectations for namespace change
eslint.config.js Enable new ESLint rule enforcement
eslint-rules/no-hardcoded-namespace-strings.js ESLint rule implementation with pattern detection
eslint-rules/README.md Comprehensive documentation for the new rule

Comment on lines 290 to +313
export function emitLifecycleEvent(event: ActionLifecycleEvent): void {
const hooks = getHooks();
if (hooks?.doAction) {
hooks.doAction(`wpk.action.${event.phase}`, event);
let eventName: string;
if (event.phase === 'start') {
eventName = WPK_EVENTS.ACTION_START;
} else if (event.phase === 'complete') {
eventName = WPK_EVENTS.ACTION_COMPLETE;
} else {
eventName = WPK_EVENTS.ACTION_ERROR;
}
hooks.doAction(eventName, event);
}

const runtime = getRuntime();
if (event.bridged && runtime?.bridge?.emit) {
runtime.bridge.emit(`wpk.action.${event.phase}`, event, event);
let eventName: string;
if (event.phase === 'start') {
eventName = WPK_EVENTS.ACTION_START;
} else if (event.phase === 'complete') {
eventName = WPK_EVENTS.ACTION_COMPLETE;
} else {
eventName = WPK_EVENTS.ACTION_ERROR;
}
Copy link

Copilot AI Oct 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The if-else chain for mapping event phases to event names is duplicated in lines 306-313. Consider extracting this logic into a helper function to reduce code duplication.

Copilot uses AI. Check for mistakes.
@pipewrk pipewrk merged commit 0f3e698 into main Oct 5, 2025
7 checks passed
@pipewrk pipewrk deleted the refactor/centralize-namespace-constants branch October 5, 2025 15:35
pipewrk added a commit that referenced this pull request Nov 8, 2025
…constants

refactor(namespace): centralize all namespace strings and add ESLint enforcement
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants