@garethdaine/agentops 0.9.0

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.
Files changed (148) hide show
  1. package/.claude-plugin/plugin.json +10 -0
  2. package/LICENSE +21 -0
  3. package/README.md +410 -0
  4. package/agents/architecture-researcher.md +115 -0
  5. package/agents/code-critic.md +190 -0
  6. package/agents/delegation-router.md +40 -0
  7. package/agents/feature-researcher.md +117 -0
  8. package/agents/interrogator.md +11 -0
  9. package/agents/pitfalls-researcher.md +112 -0
  10. package/agents/plan-validator.md +173 -0
  11. package/agents/proposer.md +61 -0
  12. package/agents/security-reviewer.md +189 -0
  13. package/agents/skill-builder.md +43 -0
  14. package/agents/spec-compliance-reviewer.md +154 -0
  15. package/agents/stack-researcher.md +89 -0
  16. package/commands/build.md +766 -0
  17. package/commands/code-analysis.md +39 -0
  18. package/commands/code-field.md +22 -0
  19. package/commands/compliance-check.md +34 -0
  20. package/commands/configure.md +178 -0
  21. package/commands/cost-report.md +17 -0
  22. package/commands/enterprise/adr.md +78 -0
  23. package/commands/enterprise/brainstorm.md +461 -0
  24. package/commands/enterprise/design.md +203 -0
  25. package/commands/enterprise/dev-setup.md +136 -0
  26. package/commands/enterprise/docker-dev.md +229 -0
  27. package/commands/enterprise/e2e.md +233 -0
  28. package/commands/enterprise/feature.md +218 -0
  29. package/commands/enterprise/gap-analysis.md +204 -0
  30. package/commands/enterprise/handover.md +195 -0
  31. package/commands/enterprise/herd.md +152 -0
  32. package/commands/enterprise/knowledge.md +173 -0
  33. package/commands/enterprise/onboard.md +86 -0
  34. package/commands/enterprise/qa-check.md +80 -0
  35. package/commands/enterprise/reason.md +196 -0
  36. package/commands/enterprise/review.md +177 -0
  37. package/commands/enterprise/scaffold.md +153 -0
  38. package/commands/enterprise/status-report.md +101 -0
  39. package/commands/enterprise/tech-catalog.md +170 -0
  40. package/commands/enterprise/test-gen.md +138 -0
  41. package/commands/evolve.md +39 -0
  42. package/commands/flags.md +44 -0
  43. package/commands/interrogate.md +263 -0
  44. package/commands/lesson.md +15 -0
  45. package/commands/lessons.md +10 -0
  46. package/commands/plan.md +44 -0
  47. package/commands/prune.md +27 -0
  48. package/commands/star.md +17 -0
  49. package/commands/supply-chain-scan.md +44 -0
  50. package/commands/unicode-scan.md +63 -0
  51. package/commands/verify.md +41 -0
  52. package/commands/workflow.md +436 -0
  53. package/hooks/ai-guardrails.sh +114 -0
  54. package/hooks/audit-log.sh +26 -0
  55. package/hooks/auto-delegate.sh +45 -0
  56. package/hooks/auto-evolve.sh +22 -0
  57. package/hooks/auto-lesson.sh +26 -0
  58. package/hooks/auto-plan.sh +59 -0
  59. package/hooks/auto-test.sh +46 -0
  60. package/hooks/auto-verify.sh +30 -0
  61. package/hooks/budget-check.sh +24 -0
  62. package/hooks/code-field-preamble.sh +30 -0
  63. package/hooks/compliance-gate.sh +50 -0
  64. package/hooks/content-trust.sh +22 -0
  65. package/hooks/credential-redact.sh +23 -0
  66. package/hooks/delegation-trust.sh +15 -0
  67. package/hooks/detect-test-run.sh +19 -0
  68. package/hooks/enforcement-lib.sh +60 -0
  69. package/hooks/evolve-gate.sh +32 -0
  70. package/hooks/evolve-lib.sh +32 -0
  71. package/hooks/exfiltration-check.sh +67 -0
  72. package/hooks/failure-collector.sh +27 -0
  73. package/hooks/feature-flags.sh +67 -0
  74. package/hooks/file-provenance.sh +31 -0
  75. package/hooks/flag-utils.sh +36 -0
  76. package/hooks/hooks.json +145 -0
  77. package/hooks/injection-scan.sh +58 -0
  78. package/hooks/integrity-verify.sh +91 -0
  79. package/hooks/lessons-check.sh +17 -0
  80. package/hooks/lockfile-audit.sh +109 -0
  81. package/hooks/patterns-lib.sh +22 -0
  82. package/hooks/plan-gate.sh +18 -0
  83. package/hooks/redact-lib.sh +15 -0
  84. package/hooks/runtime-mode.sh +56 -0
  85. package/hooks/session-cleanup.sh +74 -0
  86. package/hooks/skill-validator.sh +28 -0
  87. package/hooks/standards-enforce.sh +106 -0
  88. package/hooks/star-gate.sh +93 -0
  89. package/hooks/star-preamble.sh +10 -0
  90. package/hooks/telemetry.sh +33 -0
  91. package/hooks/todo-prune.sh +84 -0
  92. package/hooks/unicode-firewall.sh +122 -0
  93. package/hooks/unicode-lib.sh +66 -0
  94. package/hooks/unicode-scan-session.sh +96 -0
  95. package/hooks/validate-command.sh +103 -0
  96. package/hooks/validate-env.sh +51 -0
  97. package/hooks/validate-path.sh +81 -0
  98. package/package.json +40 -0
  99. package/settings.json +6 -0
  100. package/templates/ai-config/tool-standards.md +56 -0
  101. package/templates/architecture/api-first.md +192 -0
  102. package/templates/architecture/auth-patterns.md +302 -0
  103. package/templates/architecture/caching-strategy.md +359 -0
  104. package/templates/architecture/database-patterns.md +347 -0
  105. package/templates/architecture/event-driven.md +252 -0
  106. package/templates/architecture/integration-patterns.md +185 -0
  107. package/templates/architecture/multi-tenancy.md +104 -0
  108. package/templates/architecture/service-boundaries.md +200 -0
  109. package/templates/build/brief-template.md +86 -0
  110. package/templates/build/summary-template.md +100 -0
  111. package/templates/build/task-plan-template.md +133 -0
  112. package/templates/communication/effort-estimate.md +54 -0
  113. package/templates/communication/incident-response.md +59 -0
  114. package/templates/communication/post-mortem.md +109 -0
  115. package/templates/communication/risk-register.md +43 -0
  116. package/templates/communication/sprint-demo-checklist.md +64 -0
  117. package/templates/communication/stakeholder-presentation-outline.md +84 -0
  118. package/templates/communication/technical-proposal.md +77 -0
  119. package/templates/delivery/deployment/deployment-checklist.md +49 -0
  120. package/templates/delivery/design/solution-design-checklist.md +37 -0
  121. package/templates/delivery/discovery/stakeholder-questions.md +33 -0
  122. package/templates/delivery/handover/knowledge-transfer-checklist.md +75 -0
  123. package/templates/delivery/handover/operational-runbook.md +117 -0
  124. package/templates/delivery/handover/support-escalation-matrix.md +56 -0
  125. package/templates/delivery/implementation/blocker-escalation-template.md +55 -0
  126. package/templates/delivery/implementation/sprint-planning-template.md +49 -0
  127. package/templates/delivery/implementation/task-decomposition-guide.md +59 -0
  128. package/templates/delivery/qa/test-plan-template.md +76 -0
  129. package/templates/delivery/qa/test-results-template.md +55 -0
  130. package/templates/delivery/qa/uat-signoff-template.md +44 -0
  131. package/templates/governance/codeowners.md +60 -0
  132. package/templates/integration/adapter-pattern.md +160 -0
  133. package/templates/scaffolds/env-validation.md +85 -0
  134. package/templates/scaffolds/error-handling.md +171 -0
  135. package/templates/scaffolds/graceful-shutdown.md +139 -0
  136. package/templates/scaffolds/health-check.md +109 -0
  137. package/templates/scaffolds/structured-logging.md +134 -0
  138. package/templates/standards/engineering-standards.md +413 -0
  139. package/templates/standards/standards-checklist.md +125 -0
  140. package/templates/tech-catalog.json +663 -0
  141. package/templates/utilities/project-detection.md +75 -0
  142. package/templates/utilities/requirements-collection.md +68 -0
  143. package/templates/utilities/template-rendering.md +81 -0
  144. package/templates/workflows/architecture-decision.md +90 -0
  145. package/templates/workflows/bug-investigation.md +83 -0
  146. package/templates/workflows/feature-implementation.md +80 -0
  147. package/templates/workflows/refactoring.md +83 -0
  148. package/templates/workflows/spike-exploration.md +82 -0
@@ -0,0 +1,85 @@
1
+ # Enterprise Pattern: Environment Validation
2
+
3
+ Generate the following environment validation pattern adapted to the project's chosen stack.
4
+
5
+ ## Environment Schema (`src/lib/env.ts`)
6
+
7
+ ```typescript
8
+ import { z } from 'zod';
9
+
10
+ /**
11
+ * Define all required environment variables with types and defaults.
12
+ * The app will fail fast at startup if any required variable is missing or invalid.
13
+ */
14
+ const envSchema = z.object({
15
+ // App
16
+ NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
17
+ PORT: z.coerce.number().default(3000),
18
+ HOST: z.string().default('0.0.0.0'),
19
+ SERVICE_NAME: z.string().default('{{project_name}}'),
20
+ LOG_LEVEL: z.enum(['debug', 'info', 'warn', 'error', 'fatal']).default('info'),
21
+
22
+ // {{#if database}}
23
+ // Database
24
+ DATABASE_URL: z.string().url(),
25
+ // {{/if}}
26
+
27
+ // {{#if auth_strategy}}
28
+ // Auth
29
+ // AUTH_SECRET: z.string().min(32),
30
+ // {{/if}}
31
+
32
+ // Add project-specific variables below
33
+ });
34
+
35
+ export type Env = z.infer<typeof envSchema>;
36
+
37
+ function validateEnv(): Env {
38
+ const result = envSchema.safeParse(process.env);
39
+
40
+ if (!result.success) {
41
+ console.error('Environment validation failed:');
42
+ for (const issue of result.error.issues) {
43
+ console.error(` ${issue.path.join('.')}: ${issue.message}`);
44
+ }
45
+ process.exit(1);
46
+ }
47
+
48
+ return result.data;
49
+ }
50
+
51
+ /**
52
+ * Validated environment variables — import this instead of using process.env directly.
53
+ * Guarantees type safety and presence of all required variables.
54
+ */
55
+ export const env = validateEnv();
56
+ ```
57
+
58
+ ## .env.example Template
59
+
60
+ ```bash
61
+ # Application
62
+ NODE_ENV=development
63
+ PORT=3000
64
+ HOST=0.0.0.0
65
+ SERVICE_NAME={{project_name}}
66
+ LOG_LEVEL=debug
67
+
68
+ # Database (uncomment if database selected)
69
+ # DATABASE_URL=postgresql://user:password@localhost:5432/{{project_name}}?schema=public
70
+
71
+ # Authentication (uncomment if auth selected)
72
+ # AUTH_SECRET=generate-a-secret-at-least-32-chars-long
73
+ # AUTH_URL=http://localhost:3000
74
+
75
+ # External Services
76
+ # API_KEY=your-api-key-here
77
+ ```
78
+
79
+ ## Usage Notes
80
+
81
+ - Import `env` from `@/lib/env` instead of accessing `process.env` directly
82
+ - Add new environment variables to both the zod schema AND `.env.example`
83
+ - The schema validates at import time — if validation fails, the app exits immediately with clear error messages
84
+ - Use `z.coerce.number()` for numeric env vars (they're always strings in `process.env`)
85
+ - Mark optional variables with `.optional()` or `.default()`
@@ -0,0 +1,171 @@
1
+ # Enterprise Pattern: Structured Error Handling
2
+
3
+ Generate the following error handling pattern adapted to the project's chosen stack.
4
+
5
+ ## Core Error Classes (`src/lib/errors.ts`)
6
+
7
+ ```typescript
8
+ /**
9
+ * Base application error with structured metadata.
10
+ * All custom errors extend this class for consistent handling.
11
+ */
12
+ export class AppError extends Error {
13
+ public readonly statusCode: number;
14
+ public readonly code: string;
15
+ public readonly isOperational: boolean;
16
+ public readonly details?: Record<string, unknown>;
17
+
18
+ constructor(
19
+ message: string,
20
+ options: {
21
+ statusCode?: number;
22
+ code?: string;
23
+ isOperational?: boolean;
24
+ details?: Record<string, unknown>;
25
+ cause?: Error;
26
+ } = {},
27
+ ) {
28
+ super(message, { cause: options.cause });
29
+ this.name = this.constructor.name;
30
+ this.statusCode = options.statusCode ?? 500;
31
+ this.code = options.code ?? 'INTERNAL_ERROR';
32
+ this.isOperational = options.isOperational ?? true;
33
+ this.details = options.details;
34
+ Error.captureStackTrace(this, this.constructor);
35
+ }
36
+
37
+ toJSON() {
38
+ return {
39
+ error: {
40
+ code: this.code,
41
+ message: this.message,
42
+ ...(this.details && { details: this.details }),
43
+ },
44
+ };
45
+ }
46
+ }
47
+
48
+ export class ValidationError extends AppError {
49
+ constructor(message: string, details?: Record<string, unknown>) {
50
+ super(message, { statusCode: 400, code: 'VALIDATION_ERROR', details });
51
+ }
52
+ }
53
+
54
+ export class NotFoundError extends AppError {
55
+ constructor(resource: string, id?: string) {
56
+ super(id ? `${resource} with id '${id}' not found` : `${resource} not found`, {
57
+ statusCode: 404,
58
+ code: 'NOT_FOUND',
59
+ details: { resource, ...(id && { id }) },
60
+ });
61
+ }
62
+ }
63
+
64
+ export class AuthenticationError extends AppError {
65
+ constructor(message = 'Authentication required') {
66
+ super(message, { statusCode: 401, code: 'UNAUTHENTICATED' });
67
+ }
68
+ }
69
+
70
+ export class AuthorisationError extends AppError {
71
+ constructor(message = 'Insufficient permissions') {
72
+ super(message, { statusCode: 403, code: 'FORBIDDEN' });
73
+ }
74
+ }
75
+
76
+ export class ConflictError extends AppError {
77
+ constructor(message: string, details?: Record<string, unknown>) {
78
+ super(message, { statusCode: 409, code: 'CONFLICT', details });
79
+ }
80
+ }
81
+
82
+ export class RateLimitError extends AppError {
83
+ constructor(retryAfterSeconds?: number) {
84
+ super('Too many requests', {
85
+ statusCode: 429,
86
+ code: 'RATE_LIMITED',
87
+ details: retryAfterSeconds ? { retryAfter: retryAfterSeconds } : undefined,
88
+ });
89
+ }
90
+ }
91
+ ```
92
+
93
+ ## API Error Response Format
94
+
95
+ All API errors return this consistent structure:
96
+
97
+ ```json
98
+ {
99
+ "error": {
100
+ "code": "VALIDATION_ERROR",
101
+ "message": "Email address is invalid",
102
+ "details": {
103
+ "field": "email",
104
+ "value": "not-an-email"
105
+ }
106
+ }
107
+ }
108
+ ```
109
+
110
+ ## Error Handling Middleware (Express/Fastify/Hono)
111
+
112
+ ```typescript
113
+ // Adapt to the chosen framework's middleware pattern
114
+ export function errorHandler(err: Error, req: Request, res: Response, next: NextFunction) {
115
+ if (err instanceof AppError) {
116
+ logger.warn({ err, requestId: req.id }, err.message);
117
+ return res.status(err.statusCode).json(err.toJSON());
118
+ }
119
+
120
+ // Unexpected errors — log full stack, return generic message
121
+ logger.error({ err, requestId: req.id }, 'Unhandled error');
122
+ return res.status(500).json({
123
+ error: {
124
+ code: 'INTERNAL_ERROR',
125
+ message: 'An unexpected error occurred',
126
+ },
127
+ });
128
+ }
129
+ ```
130
+
131
+ ## React Error Boundary (if frontend)
132
+
133
+ ```typescript
134
+ 'use client';
135
+
136
+ import { Component, type ReactNode } from 'react';
137
+
138
+ interface Props {
139
+ children: ReactNode;
140
+ fallback?: ReactNode;
141
+ }
142
+
143
+ interface State {
144
+ hasError: boolean;
145
+ error?: Error;
146
+ }
147
+
148
+ export class ErrorBoundary extends Component<Props, State> {
149
+ state: State = { hasError: false };
150
+
151
+ static getDerivedStateFromError(error: Error): State {
152
+ return { hasError: true, error };
153
+ }
154
+
155
+ componentDidCatch(error: Error, info: React.ErrorInfo) {
156
+ console.error('ErrorBoundary caught:', error, info);
157
+ }
158
+
159
+ render() {
160
+ if (this.state.hasError) {
161
+ return this.props.fallback ?? (
162
+ <div role="alert">
163
+ <h2>Something went wrong</h2>
164
+ <p>{this.state.error?.message}</p>
165
+ </div>
166
+ );
167
+ }
168
+ return this.props.children;
169
+ }
170
+ }
171
+ ```
@@ -0,0 +1,139 @@
1
+ # Enterprise Pattern: Graceful Shutdown
2
+
3
+ Generate the following graceful shutdown pattern adapted to the project's chosen framework.
4
+
5
+ ## Shutdown Handler (`src/lib/shutdown.ts`)
6
+
7
+ ```typescript
8
+ import { logger } from '@/lib/logger';
9
+
10
+ type CleanupFn = () => Promise<void> | void;
11
+
12
+ const cleanupHandlers: Array<{ name: string; fn: CleanupFn }> = [];
13
+ let isShuttingDown = false;
14
+
15
+ /**
16
+ * Register a cleanup function to run during graceful shutdown.
17
+ * Handlers run in reverse registration order (LIFO).
18
+ *
19
+ * @example
20
+ * registerCleanup('database', async () => {
21
+ * await prisma.$disconnect();
22
+ * });
23
+ *
24
+ * registerCleanup('http-server', async () => {
25
+ * await new Promise<void>((resolve) => server.close(resolve));
26
+ * });
27
+ */
28
+ export function registerCleanup(name: string, fn: CleanupFn): void {
29
+ cleanupHandlers.push({ name, fn });
30
+ }
31
+
32
+ /**
33
+ * Execute graceful shutdown. Called automatically on SIGTERM/SIGINT.
34
+ * Runs all cleanup handlers with a timeout to prevent hanging.
35
+ */
36
+ async function shutdown(signal: string): Promise<void> {
37
+ if (isShuttingDown) return;
38
+ isShuttingDown = true;
39
+
40
+ logger.info(`Received ${signal}, starting graceful shutdown...`);
41
+
42
+ const SHUTDOWN_TIMEOUT_MS = 30_000;
43
+ const timeoutPromise = new Promise<never>((_, reject) =>
44
+ setTimeout(() => reject(new Error('Shutdown timed out')), SHUTDOWN_TIMEOUT_MS),
45
+ );
46
+
47
+ try {
48
+ // Run handlers in reverse order (LIFO)
49
+ const handlers = [...cleanupHandlers].reverse();
50
+
51
+ await Promise.race([
52
+ (async () => {
53
+ for (const handler of handlers) {
54
+ try {
55
+ logger.info(`Cleaning up: ${handler.name}`);
56
+ await handler.fn();
57
+ logger.info(`Cleaned up: ${handler.name}`);
58
+ } catch (error) {
59
+ logger.error(`Cleanup failed: ${handler.name}`, {
60
+ error: error instanceof Error ? error.message : String(error),
61
+ });
62
+ }
63
+ }
64
+ })(),
65
+ timeoutPromise,
66
+ ]);
67
+
68
+ logger.info('Graceful shutdown complete');
69
+ process.exit(0);
70
+ } catch (error) {
71
+ logger.error('Shutdown timed out, forcing exit', {
72
+ error: error instanceof Error ? error.message : String(error),
73
+ });
74
+ process.exit(1);
75
+ }
76
+ }
77
+
78
+ /**
79
+ * Install signal handlers. Call this once at application startup.
80
+ */
81
+ export function installShutdownHandlers(): void {
82
+ process.on('SIGTERM', () => shutdown('SIGTERM'));
83
+ process.on('SIGINT', () => shutdown('SIGINT'));
84
+
85
+ // Handle uncaught exceptions — log and exit
86
+ process.on('uncaughtException', (error) => {
87
+ logger.fatal('Uncaught exception', { error: error.message, stack: error.stack });
88
+ shutdown('uncaughtException');
89
+ });
90
+
91
+ // Handle unhandled promise rejections
92
+ process.on('unhandledRejection', (reason) => {
93
+ logger.fatal('Unhandled rejection', {
94
+ reason: reason instanceof Error ? reason.message : String(reason),
95
+ });
96
+ shutdown('unhandledRejection');
97
+ });
98
+ }
99
+ ```
100
+
101
+ ## Server Entry Point Integration
102
+
103
+ ```typescript
104
+ import { installShutdownHandlers, registerCleanup } from '@/lib/shutdown';
105
+ import { logger } from '@/lib/logger';
106
+ import { env } from '@/lib/env';
107
+
108
+ // Install signal handlers first
109
+ installShutdownHandlers();
110
+
111
+ // Start server
112
+ const server = app.listen(env.PORT, env.HOST, () => {
113
+ logger.info(`Server started`, { port: env.PORT, host: env.HOST, env: env.NODE_ENV });
114
+ });
115
+
116
+ // Register cleanup: close HTTP server (stop accepting new connections, drain existing)
117
+ registerCleanup('http-server', async () => {
118
+ await new Promise<void>((resolve, reject) => {
119
+ server.close((err) => (err ? reject(err) : resolve()));
120
+ });
121
+ });
122
+
123
+ // Register cleanup: disconnect database
124
+ registerCleanup('database', async () => {
125
+ // Adapt to chosen ORM:
126
+ // Prisma: await prisma.$disconnect();
127
+ // Drizzle: await pool.end();
128
+ // TypeORM: await dataSource.destroy();
129
+ });
130
+ ```
131
+
132
+ ## Usage Notes
133
+
134
+ - Always install shutdown handlers at the very start of the application entry point
135
+ - Register cleanup handlers immediately after creating resources (server, DB connections, etc.)
136
+ - LIFO order ensures the HTTP server stops accepting connections before database disconnects
137
+ - The 30-second timeout prevents infinite hangs from stuck connections
138
+ - Docker sends SIGTERM first, then SIGKILL after the grace period (default 10s, configure with `stop_grace_period`)
139
+ - In Kubernetes, set `terminationGracePeriodSeconds` to match or exceed the shutdown timeout
@@ -0,0 +1,109 @@
1
+ # Enterprise Pattern: Health Check Endpoints
2
+
3
+ Generate the following health check pattern adapted to the project's chosen framework.
4
+
5
+ ## Health Check Routes
6
+
7
+ ### Liveness Check (`/health`)
8
+
9
+ Returns 200 if the process is alive. No dependency checks — used by load balancers and container orchestrators for basic liveness.
10
+
11
+ ```typescript
12
+ // GET /health
13
+ export function healthHandler(req: Request, res: Response) {
14
+ res.status(200).json({
15
+ status: 'ok',
16
+ timestamp: new Date().toISOString(),
17
+ service: env.SERVICE_NAME,
18
+ uptime: process.uptime(),
19
+ });
20
+ }
21
+ ```
22
+
23
+ ### Readiness Check (`/health/ready`)
24
+
25
+ Returns 200 only if all dependencies are reachable. Used by orchestrators to determine if the service can accept traffic.
26
+
27
+ ```typescript
28
+ interface HealthComponent {
29
+ name: string;
30
+ status: 'healthy' | 'unhealthy' | 'degraded';
31
+ responseTimeMs?: number;
32
+ details?: Record<string, unknown>;
33
+ }
34
+
35
+ async function checkDatabase(): Promise<HealthComponent> {
36
+ const start = Date.now();
37
+ try {
38
+ // Adapt to chosen ORM/database driver
39
+ // Prisma: await prisma.$queryRaw`SELECT 1`
40
+ // Drizzle: await db.execute(sql`SELECT 1`)
41
+ // Raw: await pool.query('SELECT 1')
42
+ return {
43
+ name: 'database',
44
+ status: 'healthy',
45
+ responseTimeMs: Date.now() - start,
46
+ };
47
+ } catch (error) {
48
+ return {
49
+ name: 'database',
50
+ status: 'unhealthy',
51
+ responseTimeMs: Date.now() - start,
52
+ details: { error: error instanceof Error ? error.message : 'Unknown error' },
53
+ };
54
+ }
55
+ }
56
+
57
+ // Add more dependency checks as needed:
58
+ // async function checkRedis(): Promise<HealthComponent> { ... }
59
+ // async function checkExternalApi(): Promise<HealthComponent> { ... }
60
+
61
+ // GET /health/ready
62
+ export async function readinessHandler(req: Request, res: Response) {
63
+ const checks = await Promise.all([
64
+ checkDatabase(),
65
+ // checkRedis(),
66
+ // checkExternalApi(),
67
+ ]);
68
+
69
+ const allHealthy = checks.every((c) => c.status === 'healthy');
70
+ const hasDegraded = checks.some((c) => c.status === 'degraded');
71
+
72
+ const overallStatus = allHealthy ? 'ok' : hasDegraded ? 'degraded' : 'unavailable';
73
+ const statusCode = allHealthy ? 200 : hasDegraded ? 200 : 503;
74
+
75
+ res.status(statusCode).json({
76
+ status: overallStatus,
77
+ timestamp: new Date().toISOString(),
78
+ service: env.SERVICE_NAME,
79
+ uptime: process.uptime(),
80
+ components: checks,
81
+ });
82
+ }
83
+ ```
84
+
85
+ ## Response Format
86
+
87
+ ```json
88
+ {
89
+ "status": "ok",
90
+ "timestamp": "2026-03-17T14:00:00.000Z",
91
+ "service": "acme-portal",
92
+ "uptime": 3600.5,
93
+ "components": [
94
+ {
95
+ "name": "database",
96
+ "status": "healthy",
97
+ "responseTimeMs": 2
98
+ }
99
+ ]
100
+ }
101
+ ```
102
+
103
+ ## Usage Notes
104
+
105
+ - `/health` should be fast and dependency-free — never add database checks to liveness
106
+ - `/health/ready` should check ALL critical dependencies
107
+ - Set appropriate timeouts on dependency checks (2-5 seconds max)
108
+ - In Kubernetes: use `/health` for `livenessProbe` and `/health/ready` for `readinessProbe`
109
+ - Consider adding a `/health/startup` for slow-starting services
@@ -0,0 +1,134 @@
1
+ # Enterprise Pattern: Structured JSON Logging
2
+
3
+ Generate the following logging pattern adapted to the project's chosen stack.
4
+
5
+ ## Logger Module (`src/lib/logger.ts`)
6
+
7
+ ```typescript
8
+ import { randomUUID } from 'node:crypto';
9
+
10
+ type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'fatal';
11
+
12
+ interface LogEntry {
13
+ level: LogLevel;
14
+ message: string;
15
+ timestamp: string;
16
+ correlationId?: string;
17
+ requestId?: string;
18
+ userId?: string;
19
+ tenantId?: string;
20
+ service: string;
21
+ [key: string]: unknown;
22
+ }
23
+
24
+ const LOG_LEVELS: Record<LogLevel, number> = {
25
+ debug: 10,
26
+ info: 20,
27
+ warn: 30,
28
+ error: 40,
29
+ fatal: 50,
30
+ };
31
+
32
+ const currentLevel = (process.env.LOG_LEVEL as LogLevel) ?? 'info';
33
+
34
+ function shouldLog(level: LogLevel): boolean {
35
+ return LOG_LEVELS[level] >= LOG_LEVELS[currentLevel];
36
+ }
37
+
38
+ function formatEntry(level: LogLevel, message: string, context?: Record<string, unknown>): string {
39
+ const entry: LogEntry = {
40
+ level,
41
+ message,
42
+ timestamp: new Date().toISOString(),
43
+ service: process.env.SERVICE_NAME ?? '{{project_name}}',
44
+ ...context,
45
+ };
46
+ return JSON.stringify(entry);
47
+ }
48
+
49
+ export const logger = {
50
+ debug(message: string, context?: Record<string, unknown>) {
51
+ if (shouldLog('debug')) console.debug(formatEntry('debug', message, context));
52
+ },
53
+ info(message: string, context?: Record<string, unknown>) {
54
+ if (shouldLog('info')) console.info(formatEntry('info', message, context));
55
+ },
56
+ warn(message: string, context?: Record<string, unknown>) {
57
+ if (shouldLog('warn')) console.warn(formatEntry('warn', message, context));
58
+ },
59
+ error(message: string, context?: Record<string, unknown>) {
60
+ if (shouldLog('error')) console.error(formatEntry('error', message, context));
61
+ },
62
+ fatal(message: string, context?: Record<string, unknown>) {
63
+ if (shouldLog('fatal')) console.error(formatEntry('fatal', message, context));
64
+ },
65
+
66
+ /**
67
+ * Create a child logger with pre-bound context (e.g., per-request).
68
+ */
69
+ child(defaultContext: Record<string, unknown>) {
70
+ return {
71
+ debug: (msg: string, ctx?: Record<string, unknown>) =>
72
+ logger.debug(msg, { ...defaultContext, ...ctx }),
73
+ info: (msg: string, ctx?: Record<string, unknown>) =>
74
+ logger.info(msg, { ...defaultContext, ...ctx }),
75
+ warn: (msg: string, ctx?: Record<string, unknown>) =>
76
+ logger.warn(msg, { ...defaultContext, ...ctx }),
77
+ error: (msg: string, ctx?: Record<string, unknown>) =>
78
+ logger.error(msg, { ...defaultContext, ...ctx }),
79
+ fatal: (msg: string, ctx?: Record<string, unknown>) =>
80
+ logger.fatal(msg, { ...defaultContext, ...ctx }),
81
+ };
82
+ },
83
+ };
84
+
85
+ /**
86
+ * Generate a correlation ID for request tracing.
87
+ */
88
+ export function generateCorrelationId(): string {
89
+ return randomUUID();
90
+ }
91
+ ```
92
+
93
+ ## Request Logging Middleware
94
+
95
+ ```typescript
96
+ import { generateCorrelationId, logger } from '@/lib/logger';
97
+
98
+ // Adapt to the chosen framework's middleware pattern
99
+ export function requestLogger(req: Request, res: Response, next: NextFunction) {
100
+ const correlationId = (req.headers['x-correlation-id'] as string) ?? generateCorrelationId();
101
+ const requestId = generateCorrelationId();
102
+ const start = Date.now();
103
+
104
+ // Attach to request for downstream use
105
+ req.correlationId = correlationId;
106
+ req.requestId = requestId;
107
+
108
+ // Set response header for client correlation
109
+ res.setHeader('x-correlation-id', correlationId);
110
+ res.setHeader('x-request-id', requestId);
111
+
112
+ res.on('finish', () => {
113
+ const duration = Date.now() - start;
114
+ logger.info('request completed', {
115
+ correlationId,
116
+ requestId,
117
+ method: req.method,
118
+ path: req.path,
119
+ statusCode: res.statusCode,
120
+ durationMs: duration,
121
+ userAgent: req.headers['user-agent'],
122
+ });
123
+ });
124
+
125
+ next();
126
+ }
127
+ ```
128
+
129
+ ## Usage Notes
130
+
131
+ - In production, pipe stdout to a log aggregator (Datadog, CloudWatch, etc.)
132
+ - Use `logger.child()` to bind request context once, then log throughout the request lifecycle
133
+ - Always include `correlationId` for distributed tracing across services
134
+ - Never log sensitive data (passwords, tokens, PII) — use redaction middleware if needed