runward 0.6.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 (91) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE.md +13 -0
  3. package/README.md +128 -0
  4. package/dist/cli.js +76 -0
  5. package/dist/commands/check.js +50 -0
  6. package/dist/commands/doctor.js +75 -0
  7. package/dist/commands/init.js +90 -0
  8. package/dist/commands/status.js +49 -0
  9. package/dist/commands/update.js +48 -0
  10. package/dist/lib/constants.js +2 -0
  11. package/dist/lib/mission.js +96 -0
  12. package/dist/lib/paths.js +28 -0
  13. package/dist/lib/styles.js +60 -0
  14. package/dist/lib/tools.js +59 -0
  15. package/dist/lib/write.js +30 -0
  16. package/package.json +29 -0
  17. package/templates/mission/adr/ADR-0000-template.md +38 -0
  18. package/templates/mission/architecture.md +60 -0
  19. package/templates/mission/decision-matrix.md +37 -0
  20. package/templates/mission/evaluation-rubric.md +60 -0
  21. package/templates/mission/floor.md +47 -0
  22. package/templates/mission/framing.md +58 -0
  23. package/templates/mission/mission-contract.md +58 -0
  24. package/templates/mission/observability-schema.md +53 -0
  25. package/templates/mission/port-contract.md +63 -0
  26. package/templates/mission/reference-stack.md +70 -0
  27. package/templates/mission/runbook.md +70 -0
  28. package/templates/mission/shared-bricks.md +74 -0
  29. package/templates/mission/threat-model.md +53 -0
  30. package/templates/rules/async-job-guardrails.md +95 -0
  31. package/templates/rules/async-post-turn-pipeline.md +96 -0
  32. package/templates/rules/async-scheduled-maintenance.md +95 -0
  33. package/templates/rules/cache-three-tier-architecture.md +44 -0
  34. package/templates/rules/checklist-day-zero-project.md +97 -0
  35. package/templates/rules/checklist-pre-production-observability.md +99 -0
  36. package/templates/rules/checklist-pre-production-performance.md +109 -0
  37. package/templates/rules/checklist-pre-production-resilience.md +99 -0
  38. package/templates/rules/checklist-pre-production-security.md +86 -0
  39. package/templates/rules/config-secrets-boundary.md +57 -0
  40. package/templates/rules/config-typing-zod.md +78 -0
  41. package/templates/rules/contracts-governance.md +64 -0
  42. package/templates/rules/data-memory-consolidation.md +81 -0
  43. package/templates/rules/data-memory-invalidation.md +90 -0
  44. package/templates/rules/data-memory-scoring.md +154 -0
  45. package/templates/rules/data-migrations-forward-only.md +55 -0
  46. package/templates/rules/data-orphan-cleanup.md +142 -0
  47. package/templates/rules/data-ttl-types.md +100 -0
  48. package/templates/rules/eval-loop.md +52 -0
  49. package/templates/rules/frontier-deterministic-boundary.md +91 -0
  50. package/templates/rules/hexa-adapter-pattern.md +85 -0
  51. package/templates/rules/hexa-architecture.md +84 -0
  52. package/templates/rules/hexa-move-deterministic-out.md +93 -0
  53. package/templates/rules/hexa-recommended-stack.md +33 -0
  54. package/templates/rules/hexa-typescript-native.md +80 -0
  55. package/templates/rules/observability-alert-configuration.md +160 -0
  56. package/templates/rules/observability-llm-metrics.md +150 -0
  57. package/templates/rules/observability-startup-provider-log.md +128 -0
  58. package/templates/rules/observability-structured-json-logs.md +138 -0
  59. package/templates/rules/patterns-memory-router-tiered.md +139 -0
  60. package/templates/rules/patterns-prompt-compiler.md +126 -0
  61. package/templates/rules/patterns-request-id-propagation.md +137 -0
  62. package/templates/rules/process-adr-and-journal.md +50 -0
  63. package/templates/rules/provider-llm-auto-detection.md +54 -0
  64. package/templates/rules/provider-no-crash-missing-env.md +67 -0
  65. package/templates/rules/quality-codebase-metrics.md +115 -0
  66. package/templates/rules/quality-zod-input-validation.md +131 -0
  67. package/templates/rules/resilience-fail-open.md +65 -0
  68. package/templates/rules/resilience-multi-provider-fallback.md +100 -0
  69. package/templates/rules/resilience-retry-backoff.md +115 -0
  70. package/templates/rules/resilience-retryable-errors.md +107 -0
  71. package/templates/rules/routing-confidence-upgrade.md +89 -0
  72. package/templates/rules/routing-model-cost-ratios.md +68 -0
  73. package/templates/rules/routing-smart-complexity.md +131 -0
  74. package/templates/rules/scaling-db-connection-pooling.md +121 -0
  75. package/templates/rules/scaling-distributed-rate-limiting.md +141 -0
  76. package/templates/rules/scaling-state-externalization.md +84 -0
  77. package/templates/rules/security-prompt-injection.md +63 -0
  78. package/templates/rules/state-event-sourcing.md +66 -0
  79. package/templates/rules/tools-registry-pattern.md +153 -0
  80. package/templates/rules/tools-scope-atomicity.md +103 -0
  81. package/templates/targets/AGENTS.md +32 -0
  82. package/templates/workflows/architect.md +50 -0
  83. package/templates/workflows/brownfield.md +52 -0
  84. package/templates/workflows/decision-loop.md +52 -0
  85. package/templates/workflows/floor.md +60 -0
  86. package/templates/workflows/frame.md +69 -0
  87. package/templates/workflows/govern.md +55 -0
  88. package/templates/workflows/handover.md +55 -0
  89. package/templates/workflows/iterate.md +52 -0
  90. package/templates/workflows/method.md +53 -0
  91. package/templates/workflows/review.md +59 -0
@@ -0,0 +1,80 @@
1
+ ---
2
+ title: A Thin Model Abstraction You Own (Not a Heavy Chain Framework)
3
+ impact: HIGH
4
+ impactDescription: Reduces token overhead, improves debuggability, removes volatile dependencies by keeping a light abstraction you control instead of a heavy framework
5
+ tags: [architecture, typescript, llm, frameworks]
6
+ ---
7
+
8
+ ## A Thin Model Abstraction You Own
9
+
10
+ Heavy chain or agent frameworks add overhead that is rarely justified. Keep a light abstraction you control over the model, and call the provider through a direct SDK behind your own port.
11
+
12
+ **Why avoid a heavy chain framework as the default:**
13
+
14
+ 1. **Token overhead** - the framework's internal prompts add to your cost.
15
+ 2. **Abstraction leaks** - you cannot optimise what you cannot see through.
16
+ 3. **Volatile ecosystem** - frequent breaking changes in fast-moving framework code.
17
+ 4. **Debug difficulty** - stack traces run through framework internals.
18
+ 5. **Unnecessary complexity** - most applications do not need generic chains or agents.
19
+
20
+ **Incorrect (a heavy framework owns your control flow):**
21
+
22
+ ```typescript
23
+ import { ChatModel } from "@chain-framework/core";
24
+ import { HumanMessage, SystemMessage } from "@chain-framework/core/messages";
25
+ import { StringOutputParser } from "@chain-framework/core/output_parsers";
26
+
27
+ const model = new ChatModel({ modelName: "mid-tier-model" });
28
+ const chain = model.pipe(new StringOutputParser());
29
+
30
+ const response = await chain.invoke([
31
+ new SystemMessage(systemPrompt),
32
+ new HumanMessage(userMessage),
33
+ ]);
34
+ // You no longer control the prompt, the caching, or the cost.
35
+ ```
36
+
37
+ **Correct (a direct SDK behind a thin port you own):**
38
+
39
+ ```typescript
40
+ // The adapter imports one provider SDK; the core sees only the port.
41
+ import { ModelSDK } from "@vendor/model-sdk"; // any provider's official SDK
42
+
43
+ const client = new ModelSDK();
44
+
45
+ const response = await client.complete({
46
+ model: "mid-tier-model",
47
+ maxTokens: 4096,
48
+ system: [corePrompt, toolsPrompt, dynamicContext],
49
+ messages: [{ role: "user", content: userMessage }],
50
+ });
51
+ ```
52
+
53
+ **When a framework might still help:**
54
+
55
+ - Rapid throwaway prototyping.
56
+ - Genuinely complex multi-agent orchestration that you have measured the need for.
57
+
58
+ **The abstraction you keep is yours:**
59
+
60
+ ```typescript
61
+ // A thin interface you control, in the core. One adapter per provider implements it.
62
+ interface ModelPort {
63
+ complete(request: CompletionRequest): Promise<CompletionResponse>;
64
+ }
65
+
66
+ class PrimaryModelAdapter implements ModelPort {
67
+ // full control over caching, retries, logging, cost attribution
68
+ }
69
+
70
+ class SecondaryModelAdapter implements ModelPort {
71
+ // same interface, a different provider, swapped by configuration
72
+ }
73
+ ```
74
+
75
+ **Benefits of the thin, owned abstraction:**
76
+
77
+ - Full control of caching and cost attribution.
78
+ - Simple debugging (no framework internals in the trace).
79
+ - No framework learning curve or breaking-change treadmill.
80
+ - The type safety and the contract are yours to define.
@@ -0,0 +1,160 @@
1
+ ---
2
+ title: Alert Configuration
3
+ impact: MEDIUM
4
+ impactDescription: Ensures timely notification of production issues
5
+ tags: [observability, alerting, production]
6
+ ---
7
+
8
+ ## Alert Configuration
9
+
10
+ Configure alerts to catch issues before they impact users.
11
+
12
+ **Alert Tiers:**
13
+
14
+ | Tier | Response Time | Examples |
15
+ |------|---------------|----------|
16
+ | P0 - Critical | <15 min | Service down, data loss |
17
+ | P1 - High | <1 hour | Error rate spike, degraded performance |
18
+ | P2 - Medium | <4 hours | Elevated latency, cache degradation |
19
+ | P3 - Low | Next business day | Cost anomaly, capacity warning |
20
+
21
+ **Recommended Alerts:**
22
+
23
+ ```yaml
24
+ # alerts.yaml
25
+ alerts:
26
+ # P0 - Critical
27
+ - name: service_down
28
+ condition: healthcheck_failures > 3
29
+ window: 5m
30
+ severity: critical
31
+ notify: [pagerduty, slack-critical]
32
+
33
+ - name: error_rate_critical
34
+ condition: error_rate > 10%
35
+ window: 5m
36
+ severity: critical
37
+ notify: [pagerduty, slack-critical]
38
+
39
+ # P1 - High
40
+ - name: error_rate_elevated
41
+ condition: error_rate > 5%
42
+ window: 5m
43
+ severity: high
44
+ notify: [slack-alerts]
45
+
46
+ - name: llm_provider_failing
47
+ condition: llm_error_rate > 5%
48
+ window: 5m
49
+ severity: high
50
+ notify: [slack-alerts]
51
+
52
+ - name: latency_degraded
53
+ condition: p95_latency > 5000ms
54
+ window: 10m
55
+ severity: high
56
+ notify: [slack-alerts]
57
+
58
+ # P2 - Medium
59
+ - name: cache_hit_low
60
+ condition: cache_hit_rate < 50%
61
+ window: 30m
62
+ severity: medium
63
+ notify: [slack-engineering]
64
+
65
+ - name: db_connections_high
66
+ condition: db_pool_usage > 80%
67
+ window: 15m
68
+ severity: medium
69
+ notify: [slack-engineering]
70
+
71
+ # P3 - Low
72
+ - name: daily_cost_anomaly
73
+ condition: daily_cost > 2x avg_daily_cost
74
+ window: 24h
75
+ severity: low
76
+ notify: [email-team]
77
+
78
+ - name: disk_space_warning
79
+ condition: disk_usage > 70%
80
+ window: 1h
81
+ severity: low
82
+ notify: [slack-engineering]
83
+ ```
84
+
85
+ **Implementation with the telemetry sink:**
86
+
87
+ ```typescript
88
+ // Alert configuration in code
89
+ const alertRules = [
90
+ {
91
+ name: 'High Error Rate',
92
+ query: `
93
+ SELECT count(*) as errors
94
+ FROM logs
95
+ WHERE level = 'error'
96
+ AND timestamp > now() - 5m
97
+ `,
98
+ threshold: { errors: 100 },
99
+ severity: 'high',
100
+ channels: ['slack-alerts'],
101
+ },
102
+ {
103
+ name: 'LLM Cost Spike',
104
+ query: `
105
+ SELECT sum(estimatedCost) as cost
106
+ FROM llm_metrics
107
+ WHERE timestamp > now() - 1h
108
+ `,
109
+ threshold: { cost: 50 },
110
+ severity: 'medium',
111
+ channels: ['slack-engineering'],
112
+ },
113
+ ];
114
+
115
+ // Alert evaluation
116
+ async function evaluateAlerts() {
117
+ for (const rule of alertRules) {
118
+ const result = await telemetry.query(rule.query);
119
+
120
+ if (exceedsThreshold(result, rule.threshold)) {
121
+ await sendAlert({
122
+ name: rule.name,
123
+ severity: rule.severity,
124
+ channels: rule.channels,
125
+ data: result,
126
+ });
127
+ }
128
+ }
129
+ }
130
+
131
+ // Run every minute
132
+ cron.schedule('* * * * *', evaluateAlerts);
133
+ ```
134
+
135
+ **Alert Message Format:**
136
+
137
+ ```typescript
138
+ interface AlertMessage {
139
+ title: string;
140
+ severity: 'critical' | 'high' | 'medium' | 'low';
141
+ summary: string;
142
+ metrics: Record<string, number>;
143
+ runbook?: string;
144
+ dashboardUrl?: string;
145
+ }
146
+
147
+ // Example Slack message
148
+ const message = {
149
+ title: 'High Error Rate Detected',
150
+ severity: 'high',
151
+ summary: 'Error rate exceeded 5% threshold for 5 minutes',
152
+ metrics: {
153
+ errorRate: 7.2,
154
+ errorCount: 156,
155
+ affectedEndpoints: 3,
156
+ },
157
+ runbook: 'https://wiki/runbooks/high-error-rate',
158
+ dashboardUrl: 'https://grafana/d/errors',
159
+ };
160
+ ```
@@ -0,0 +1,150 @@
1
+ ---
2
+ title: LLM Metrics Tracking
3
+ impact: MEDIUM
4
+ impactDescription: Enables cost monitoring, performance optimization, and anomaly detection
5
+ tags: [observability, llm, metrics, cost]
6
+ ---
7
+
8
+ ## LLM Metrics Tracking
9
+
10
+ Track comprehensive metrics for every LLM call to enable cost control and optimization.
11
+
12
+ **Essential Metrics:**
13
+
14
+ ```typescript
15
+ interface LLMMetrics {
16
+ // Request metadata
17
+ requestId: string;
18
+ userId: string;
19
+ model: string;
20
+ provider: string;
21
+
22
+ // Token usage
23
+ inputTokens: number;
24
+ outputTokens: number;
25
+ cacheReadTokens: number;
26
+ cacheWriteTokens: number;
27
+
28
+ // Performance
29
+ latencyMs: number;
30
+ timeToFirstToken?: number;
31
+
32
+ // Cost
33
+ estimatedCost: number;
34
+
35
+ // Context
36
+ complexity: string;
37
+ cached: boolean;
38
+ retries: number;
39
+
40
+ // Timestamp
41
+ timestamp: Date;
42
+ }
43
+ ```
44
+
45
+ **Implementation:**
46
+
47
+ ```typescript
48
+ // LLM wrapper with metrics
49
+ async function completeLLM(request: LLMRequest): Promise<LLMResponse> {
50
+ const start = Date.now();
51
+ const metrics: Partial<LLMMetrics> = {
52
+ requestId: request.requestId,
53
+ userId: request.userId,
54
+ model: request.model,
55
+ provider: llmConfig.provider,
56
+ complexity: request.complexity,
57
+ timestamp: new Date(),
58
+ retries: 0,
59
+ };
60
+
61
+ try {
62
+ const response = await withLLMRetry(
63
+ () => llmClient.complete(request),
64
+ { onRetry: () => metrics.retries!++ }
65
+ );
66
+
67
+ // Extract usage
68
+ metrics.inputTokens = response.usage.input_tokens;
69
+ metrics.outputTokens = response.usage.output_tokens;
70
+ metrics.cacheReadTokens = response.usage.cache_read_input_tokens || 0;
71
+ metrics.cacheWriteTokens = response.usage.cache_creation_input_tokens || 0;
72
+ metrics.latencyMs = Date.now() - start;
73
+ metrics.cached = metrics.cacheReadTokens > 0;
74
+ metrics.estimatedCost = calculateCost(metrics as LLMMetrics);
75
+
76
+ // Log metrics
77
+ logger.info('LLM request completed', metrics);
78
+
79
+ // Send to metrics service
80
+ telemetry.record('llm_request', metrics);
81
+
82
+ return response;
83
+ } catch (error) {
84
+ logger.error('LLM request failed', {
85
+ ...metrics,
86
+ error: error.message,
87
+ latencyMs: Date.now() - start,
88
+ });
89
+ throw error;
90
+ }
91
+ }
92
+
93
+ // Cost calculation.
94
+ // Typical cache pricing: reads at ~10% of the input price, writes at
95
+ // ~125% (verify against your provider's price sheet).
96
+ // CAUTION: usage field semantics vary by provider — some exclude cache
97
+ // read/write tokens from input_tokens, others include them. This formula
98
+ // assumes input_tokens EXCLUDES cache tokens; check your provider's
99
+ // usage schema before trusting the numbers, or you will double-count.
100
+ function calculateCost(metrics: LLMMetrics): number {
101
+ const pricing = MODEL_PRICING[metrics.model];
102
+
103
+ return (
104
+ (metrics.inputTokens * pricing.input +
105
+ metrics.cacheReadTokens * pricing.input * 0.1 +
106
+ metrics.cacheWriteTokens * pricing.input * 1.25 +
107
+ metrics.outputTokens * pricing.output) / 1_000_000
108
+ );
109
+ }
110
+ ```
111
+
112
+ **Aggregated Metrics:**
113
+
114
+ ```typescript
115
+ // Daily cost aggregation
116
+ interface DailyMetrics {
117
+ date: string;
118
+ totalRequests: number;
119
+ totalCost: number;
120
+ avgLatency: number;
121
+ cacheHitRate: number;
122
+ modelBreakdown: Record<string, { requests: number; cost: number }>;
123
+ errorRate: number;
124
+ }
125
+
126
+ async function aggregateDailyMetrics(date: string): Promise<DailyMetrics> {
127
+ const metrics = await db.llmMetrics.findMany({
128
+ where: { date: startOfDay(date) },
129
+ });
130
+
131
+ return {
132
+ date,
133
+ totalRequests: metrics.length,
134
+ totalCost: sum(metrics.map(m => m.estimatedCost)),
135
+ avgLatency: avg(metrics.map(m => m.latencyMs)),
136
+ cacheHitRate: metrics.filter(m => m.cached).length / metrics.length,
137
+ modelBreakdown: groupByModel(metrics),
138
+ errorRate: metrics.filter(m => m.error).length / metrics.length,
139
+ };
140
+ }
141
+ ```
142
+
143
+ **Alert Thresholds:**
144
+
145
+ | Metric | Warning | Critical |
146
+ |--------|---------|----------|
147
+ | Cost/day | >$50 | >$100 |
148
+ | Error rate | >2% | >5% |
149
+ | P95 latency | >5s | >10s |
150
+ | Cache hit rate | <50% | <30% |
@@ -0,0 +1,128 @@
1
+ ---
2
+ title: Log Providers at Startup
3
+ impact: LOW
4
+ impactDescription: Simplifies debugging by showing configuration at startup
5
+ tags: [observability, configuration, debugging]
6
+ ---
7
+
8
+ ## Log Providers at Startup
9
+
10
+ Always log the detected configuration at application startup for debugging.
11
+
12
+ **Implementation:**
13
+
14
+ ```typescript
15
+ // src/lib/startup.ts
16
+ import { features } from './features';
17
+ import { llmConfig } from './providers/llm-provider';
18
+ import { embeddingConfig } from './providers/embedding-provider';
19
+
20
+ export function logStartupConfig() {
21
+ console.info('═'.repeat(60));
22
+ console.info('[STARTUP] Application Configuration');
23
+ console.info('═'.repeat(60));
24
+
25
+ console.info('[CONFIG] Deployment mode:', features.mode);
26
+
27
+ console.info('[CONFIG] Providers:', {
28
+ llm: {
29
+ provider: llmConfig.provider,
30
+ model: llmConfig.model,
31
+ // Don't log API keys
32
+ },
33
+ embeddings: {
34
+ provider: embeddingConfig.provider,
35
+ model: embeddingConfig.model,
36
+ dimensions: embeddingConfig.dimensions,
37
+ },
38
+ });
39
+
40
+ console.info('[CONFIG] Features:', {
41
+ auth: features.auth.provider,
42
+ search: features.search.enabled ? features.search.provider : 'disabled',
43
+ memory: features.memory.enabled ? features.memory.provider : 'disabled',
44
+ realtime: features.realtime.enabled,
45
+ presence: features.realtime.presence,
46
+ });
47
+
48
+ console.info('[CONFIG] Database:', {
49
+ postgres: !!process.env.DATABASE_URL,
50
+ graph: !!process.env.GRAPH_URI,
51
+ redis: !!process.env.REDIS_URL,
52
+ });
53
+
54
+ console.info('═'.repeat(60));
55
+ }
56
+
57
+ // Call at startup
58
+ // app.ts or index.ts
59
+ logStartupConfig();
60
+ ```
61
+
62
+ **Output Example:**
63
+
64
+ ```
65
+ ════════════════════════════════════════════════════════════
66
+ [STARTUP] Application Configuration
67
+ ════════════════════════════════════════════════════════════
68
+ [CONFIG] Deployment mode: full
69
+ [CONFIG] Providers: {
70
+ llm: { provider: 'configured-provider', model: 'mid-tier-model' },
71
+ embeddings: { provider: 'configured-provider', model: 'embedding-model', dimensions: 1536 }
72
+ }
73
+ [CONFIG] Features: {
74
+ auth: 'configured-provider',
75
+ search: 'web-search',
76
+ memory: 'configured-provider',
77
+ realtime: true,
78
+ realtime: true
79
+ }
80
+ [CONFIG] Database: { postgres: true, graph: true, redis: true }
81
+ ════════════════════════════════════════════════════════════
82
+ ```
83
+
84
+ **Environment Validation:**
85
+
86
+ ```typescript
87
+ function validateEnvironment() {
88
+ const errors: string[] = [];
89
+ const warnings: string[] = [];
90
+
91
+ // Required
92
+ if (!process.env.DATABASE_URL) {
93
+ errors.push('DATABASE_URL is required');
94
+ }
95
+
96
+ // At least one LLM provider
97
+ const hasLLM = [
98
+ 'MODEL_GATEWAY_URL',
99
+ 'MODEL_PROVIDER',
100
+ 'AWS_ACCESS_KEY_ID',
101
+ 'MODEL_API_KEY',
102
+ 'EMBEDDING_API_KEY',
103
+ ].some(key => !!process.env[key]);
104
+
105
+ if (!hasLLM) {
106
+ errors.push('No LLM provider configured');
107
+ }
108
+
109
+ // Warnings for missing optional services
110
+ if (!process.env.REDIS_URL) {
111
+ warnings.push('REDIS_URL not set - using in-memory state (not scalable)');
112
+ }
113
+
114
+ // Log results
115
+ if (warnings.length > 0) {
116
+ console.warn('[STARTUP] Warnings:', warnings);
117
+ }
118
+
119
+ if (errors.length > 0) {
120
+ console.error('[STARTUP] Configuration errors:', errors);
121
+ process.exit(1);
122
+ }
123
+ }
124
+
125
+ // Run before startup
126
+ validateEnvironment();
127
+ logStartupConfig();
128
+ ```
@@ -0,0 +1,138 @@
1
+ ---
2
+ title: Structured JSON Logs
3
+ impact: MEDIUM
4
+ impactDescription: Enables log querying, aggregation, and alerting in production
5
+ tags: [observability, logging, production]
6
+ ---
7
+
8
+ ## Structured JSON Logs
9
+
10
+ Use structured JSON logs for queryable, parseable logging in production.
11
+
12
+ **Incorrect:**
13
+
14
+ ```typescript
15
+ // Unstructured logs - hard to parse and query
16
+ console.log(`User ${userId} created project ${projectId} in ${duration}ms`);
17
+ console.log('Error:', error.message);
18
+ console.log(`Request from ${req.ip} to ${req.path}`);
19
+ ```
20
+
21
+ **Correct:**
22
+
23
+ ```typescript
24
+ // Structured logger
25
+ interface LogEntry {
26
+ level: 'debug' | 'info' | 'warn' | 'error';
27
+ message: string;
28
+ timestamp: string;
29
+ requestId?: string;
30
+ userId?: string;
31
+ tenantId?: string;
32
+ [key: string]: unknown;
33
+ }
34
+
35
+ class StructuredLogger {
36
+ private context: Partial<LogEntry> = {};
37
+
38
+ child(context: Partial<LogEntry>): StructuredLogger {
39
+ const child = new StructuredLogger();
40
+ child.context = { ...this.context, ...context };
41
+ return child;
42
+ }
43
+
44
+ private log(level: LogEntry['level'], message: string, data: object = {}) {
45
+ const entry: LogEntry = {
46
+ level,
47
+ message,
48
+ timestamp: new Date().toISOString(),
49
+ ...this.context,
50
+ ...data,
51
+ };
52
+
53
+ // Redact sensitive fields
54
+ const redacted = this.redactSensitive(entry);
55
+
56
+ console.log(JSON.stringify(redacted));
57
+ }
58
+
59
+ private redactSensitive(entry: LogEntry): LogEntry {
60
+ const sensitiveKeys = ['password', 'token', 'apiKey', 'secret', 'authorization'];
61
+ const redacted = { ...entry };
62
+
63
+ for (const key of Object.keys(redacted)) {
64
+ if (sensitiveKeys.some(s => key.toLowerCase().includes(s))) {
65
+ redacted[key] = '[REDACTED]';
66
+ }
67
+ }
68
+
69
+ return redacted;
70
+ }
71
+
72
+ debug(message: string, data?: object) { this.log('debug', message, data); }
73
+ info(message: string, data?: object) { this.log('info', message, data); }
74
+ warn(message: string, data?: object) { this.log('warn', message, data); }
75
+ error(message: string, data?: object) { this.log('error', message, data); }
76
+ }
77
+
78
+ export const logger = new StructuredLogger();
79
+
80
+ // Usage
81
+ logger.info('Project created', {
82
+ userId: 'usr_123',
83
+ projectId: 'prj_456',
84
+ duration: 45,
85
+ });
86
+
87
+ // Output:
88
+ // {"level":"info","message":"Project created","timestamp":"2025-01-24T10:30:00.000Z","userId":"usr_123","projectId":"prj_456","duration":45}
89
+ ```
90
+
91
+ **Request Context Logger:**
92
+
93
+ ```typescript
94
+ // Middleware to add request context
95
+ function requestLoggerMiddleware(req: Request, res: Response, next: NextFunction) {
96
+ const requestId = req.headers['x-request-id'] as string || crypto.randomUUID();
97
+ const requestLogger = logger.child({
98
+ requestId,
99
+ method: req.method,
100
+ path: req.path,
101
+ userId: req.user?.id,
102
+ tenantId: req.user?.tenantId,
103
+ });
104
+
105
+ req.logger = requestLogger;
106
+
107
+ // Log request start
108
+ requestLogger.info('Request started');
109
+
110
+ // Log request end
111
+ const start = Date.now();
112
+ res.on('finish', () => {
113
+ requestLogger.info('Request completed', {
114
+ status: res.statusCode,
115
+ duration: Date.now() - start,
116
+ });
117
+ });
118
+
119
+ next();
120
+ }
121
+ ```
122
+
123
+ **Log Query Examples (the telemetry sink/Datadog):**
124
+
125
+ ```sql
126
+ -- Find slow LLM requests
127
+ SELECT * FROM logs
128
+ WHERE message = 'LLM request completed'
129
+ AND duration > 5000
130
+ ORDER BY timestamp DESC
131
+
132
+ -- Error rate by endpoint
133
+ SELECT path, COUNT(*) as errors
134
+ FROM logs
135
+ WHERE level = 'error'
136
+ GROUP BY path
137
+ ORDER BY errors DESC
138
+ ```