claude-flow-novice 2.16.0 → 2.16.1

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 (154) hide show
  1. package/.claude/cfn-extras/skills/GOOGLE_SHEETS_SKILLS_README.md +1 -1
  2. package/.claude/cfn-extras/skills/google-sheets-api-coordinator/SKILL.md +1 -1
  3. package/.claude/cfn-extras/skills/google-sheets-formula-builder/SKILL.md +1 -1
  4. package/.claude/cfn-extras/skills/google-sheets-progress/SKILL.md +1 -1
  5. package/.claude/commands/CFN_LOOP_FRONTEND.md +1 -1
  6. package/.claude/commands/cfn-loop-cli.md +124 -46
  7. package/.claude/commands/cfn-loop-frontend.md +1 -1
  8. package/.claude/commands/cfn-loop-task.md +2 -2
  9. package/.claude/commands/deprecated/cfn-loop.md +2 -2
  10. package/.claude/hooks/cfn-invoke-post-edit.sh +31 -5
  11. package/.claude/hooks/cfn-post-edit.config.json +9 -2
  12. package/.claude/root-claude-distribute/CFN-CLAUDE.md +1 -1
  13. package/.claude/skills/cfn-backlog-management/SKILL.md +1 -1
  14. package/.claude/skills/cfn-loop-orchestration/NORTH_STAR_INDEX.md +1 -1
  15. package/claude-assets/agents/cfn-dev-team/analysts/root-cause-analyst.md +2 -2
  16. package/claude-assets/agents/cfn-dev-team/architecture/base-template-generator.md +1 -1
  17. package/claude-assets/agents/cfn-dev-team/coordinators/cfn-frontend-coordinator.md +2 -2
  18. package/claude-assets/agents/cfn-dev-team/coordinators/handoff-coordinator.md +1 -1
  19. package/claude-assets/agents/cfn-dev-team/dev-ops/devops-engineer.md +1 -1
  20. package/claude-assets/agents/cfn-dev-team/dev-ops/docker-specialist.md +2 -2
  21. package/claude-assets/agents/cfn-dev-team/dev-ops/github-commit-agent.md +2 -2
  22. package/claude-assets/agents/cfn-dev-team/dev-ops/kubernetes-specialist.md +1 -1
  23. package/claude-assets/agents/cfn-dev-team/developers/api-gateway-specialist.md +1 -1
  24. package/claude-assets/agents/cfn-dev-team/developers/data/data-engineer.md +1 -1
  25. package/claude-assets/agents/cfn-dev-team/developers/database/database-architect.md +1 -1
  26. package/claude-assets/agents/cfn-dev-team/developers/frontend/typescript-specialist.md +1 -1
  27. package/claude-assets/agents/cfn-dev-team/developers/frontend/ui-designer.md +1 -1
  28. package/claude-assets/agents/cfn-dev-team/developers/graphql-specialist.md +1 -1
  29. package/claude-assets/agents/cfn-dev-team/documentation/pseudocode.md +1 -1
  30. package/claude-assets/agents/cfn-dev-team/product-owners/accessibility-advocate-persona.md +1 -1
  31. package/claude-assets/agents/cfn-dev-team/product-owners/cto-agent.md +1 -1
  32. package/claude-assets/agents/cfn-dev-team/product-owners/power-user-persona.md +1 -1
  33. package/claude-assets/agents/cfn-dev-team/reviewers/quality/security-specialist.md +1 -1
  34. package/claude-assets/agents/cfn-dev-team/testers/api-testing-specialist.md +1 -1
  35. package/claude-assets/agents/cfn-dev-team/testers/chaos-engineering-specialist.md +1 -1
  36. package/claude-assets/agents/cfn-dev-team/testers/contract-tester.md +1 -1
  37. package/claude-assets/agents/cfn-dev-team/testers/e2e/playwright-tester.md +1 -1
  38. package/claude-assets/agents/cfn-dev-team/testers/integration-tester.md +1 -1
  39. package/claude-assets/agents/cfn-dev-team/testers/load-testing-specialist.md +1 -1
  40. package/claude-assets/agents/cfn-dev-team/testers/mutation-testing-specialist.md +1 -1
  41. package/claude-assets/agents/cfn-dev-team/testers/unit/tdd-london-unit-swarm.md +1 -1
  42. package/claude-assets/agents/cfn-dev-team/utility/agent-builder.md +11 -0
  43. package/claude-assets/agents/cfn-dev-team/utility/analyst.md +1 -1
  44. package/claude-assets/agents/cfn-dev-team/utility/claude-code-expert.md +1 -1
  45. package/claude-assets/agents/cfn-dev-team/utility/epic-creator.md +1 -1
  46. package/claude-assets/agents/cfn-dev-team/utility/memory-leak-specialist.md +1 -1
  47. package/claude-assets/agents/cfn-dev-team/utility/researcher.md +1 -1
  48. package/claude-assets/agents/cfn-dev-team/utility/z-ai-specialist.md +1 -1
  49. package/claude-assets/agents/custom/cfn-docker-expert.md +1 -0
  50. package/claude-assets/agents/custom/cfn-loops-cli-expert.md +326 -17
  51. package/claude-assets/agents/custom/cfn-redis-operations.md +529 -529
  52. package/claude-assets/agents/custom/cfn-system-expert.md +1 -1
  53. package/claude-assets/agents/custom/trigger-dev-expert.md +369 -0
  54. package/claude-assets/agents/docker-team/micro-sprint-planner.md +747 -747
  55. package/claude-assets/agents/project-only-agents/npm-package-specialist.md +1 -1
  56. package/claude-assets/cfn-extras/skills/GOOGLE_SHEETS_SKILLS_README.md +1 -1
  57. package/claude-assets/cfn-extras/skills/google-sheets-api-coordinator/SKILL.md +1 -1
  58. package/claude-assets/cfn-extras/skills/google-sheets-formula-builder/SKILL.md +1 -1
  59. package/claude-assets/cfn-extras/skills/google-sheets-progress/SKILL.md +1 -1
  60. package/claude-assets/commands/CFN_LOOP_FRONTEND.md +1 -1
  61. package/claude-assets/commands/cfn-loop-cli.md +124 -46
  62. package/claude-assets/commands/cfn-loop-frontend.md +1 -1
  63. package/claude-assets/commands/cfn-loop-task.md +2 -2
  64. package/claude-assets/commands/deprecated/cfn-loop.md +2 -2
  65. package/claude-assets/hooks/GIT-HOOKS-USAGE-EXAMPLES.md +116 -0
  66. package/claude-assets/hooks/README-GIT-HOOKS.md +443 -0
  67. package/claude-assets/hooks/cfn-invoke-post-edit.sh +31 -5
  68. package/claude-assets/hooks/cfn-post-edit.config.json +9 -2
  69. package/claude-assets/hooks/install-git-hooks.sh +243 -0
  70. package/claude-assets/hooks/subagent-start.sh +98 -0
  71. package/claude-assets/hooks/subagent-stop.sh +93 -0
  72. package/claude-assets/hooks/validators/credential-scanner.sh +172 -0
  73. package/claude-assets/root-claude-distribute/CFN-CLAUDE.md +1 -1
  74. package/claude-assets/skills/cfn-backlog-management/SKILL.md +1 -1
  75. package/claude-assets/skills/cfn-dependency-ingestion/SKILL.md +41 -13
  76. package/claude-assets/skills/cfn-dependency-ingestion/ingest.sh +237 -0
  77. package/claude-assets/skills/cfn-dependency-ingestion/manifests/cli-mode-dependencies.txt +73 -0
  78. package/claude-assets/skills/cfn-dependency-ingestion/manifests/shared-dependencies.txt +57 -0
  79. package/claude-assets/skills/cfn-dependency-ingestion/manifests/trigger-dev-dependencies.txt +82 -0
  80. package/claude-assets/skills/cfn-dependency-ingestion/manifests/trigger-mode-dependencies.txt +80 -0
  81. package/claude-assets/skills/cfn-environment-sanitization/sanitize-environment.sh +14 -4
  82. package/claude-assets/skills/cfn-loop-orchestration/NORTH_STAR_INDEX.md +1 -1
  83. package/claude-assets/skills/cfn-provider-routing/SKILL.md +23 -0
  84. package/claude-assets/skills/docker-build/build.sh +1 -1
  85. package/dist/agent/skill-mcp-selector.js +2 -1
  86. package/dist/agent/skill-mcp-selector.js.map +1 -1
  87. package/dist/agents/agent-loader.js +165 -146
  88. package/dist/agents/agent-loader.js.map +1 -1
  89. package/dist/cli/agent-executor.js +470 -26
  90. package/dist/cli/agent-executor.js.map +1 -1
  91. package/dist/cli/agent-prompt-builder.js +2 -2
  92. package/dist/cli/agent-prompt-builder.js.map +1 -1
  93. package/dist/cli/agent-spawn.js +7 -4
  94. package/dist/cli/agent-spawn.js.map +1 -1
  95. package/dist/cli/agent-spawner.js +51 -4
  96. package/dist/cli/agent-spawner.js.map +1 -1
  97. package/dist/cli/agent-token-manager.js +2 -1
  98. package/dist/cli/agent-token-manager.js.map +1 -1
  99. package/dist/cli/anthropic-client.js +117 -11
  100. package/dist/cli/anthropic-client.js.map +1 -1
  101. package/dist/cli/cfn-context.js +2 -1
  102. package/dist/cli/cfn-context.js.map +1 -1
  103. package/dist/cli/cfn-metrics.js +2 -1
  104. package/dist/cli/cfn-metrics.js.map +1 -1
  105. package/dist/cli/cfn-redis.js +2 -1
  106. package/dist/cli/cfn-redis.js.map +1 -1
  107. package/dist/cli/cli-agent-context.js +2 -0
  108. package/dist/cli/cli-agent-context.js.map +1 -1
  109. package/dist/cli/config-manager.js +4 -252
  110. package/dist/cli/config-manager.js.map +1 -1
  111. package/dist/cli/conversation-fork-cleanup.js +2 -1
  112. package/dist/cli/conversation-fork-cleanup.js.map +1 -1
  113. package/dist/cli/conversation-fork.js +2 -1
  114. package/dist/cli/conversation-fork.js.map +1 -1
  115. package/dist/cli/coordination/agent-messaging.js +415 -0
  116. package/dist/cli/coordination/agent-messaging.js.map +1 -0
  117. package/dist/cli/coordination/wait-for-threshold.js +232 -0
  118. package/dist/cli/coordination/wait-for-threshold.js.map +1 -0
  119. package/dist/cli/iteration-history.js +2 -1
  120. package/dist/cli/iteration-history.js.map +1 -1
  121. package/dist/cli/process-lifecycle.js +5 -1
  122. package/dist/cli/process-lifecycle.js.map +1 -1
  123. package/dist/cli/spawn-agent-cli.js +41 -6
  124. package/dist/cli/spawn-agent-cli.js.map +1 -1
  125. package/dist/coordination/redis-waiting-mode.js +4 -0
  126. package/dist/coordination/redis-waiting-mode.js.map +1 -1
  127. package/dist/lib/artifact-registry.js +4 -0
  128. package/dist/lib/artifact-registry.js.map +1 -1
  129. package/dist/lib/connection-pool.js +390 -0
  130. package/dist/lib/connection-pool.js.map +1 -0
  131. package/dist/lib/environment-contract.js +258 -0
  132. package/dist/lib/environment-contract.js.map +1 -0
  133. package/dist/lib/query-optimizer.js +388 -0
  134. package/dist/lib/query-optimizer.js.map +1 -0
  135. package/dist/lib/result-cache.js +285 -0
  136. package/dist/lib/result-cache.js.map +1 -0
  137. package/dist/mcp/auth-middleware.js +2 -1
  138. package/dist/mcp/auth-middleware.js.map +1 -1
  139. package/dist/mcp/playwright-mcp-server-auth.js +2 -1
  140. package/dist/mcp/playwright-mcp-server-auth.js.map +1 -1
  141. package/package.json +3 -1
  142. package/scripts/build-agent-image.sh +1 -1
  143. package/scripts/cost-allocation-tracker.sh +632 -0
  144. package/scripts/docker-rebuild-all-agents.sh +2 -2
  145. package/scripts/reorganize-tests.sh +280 -0
  146. package/scripts/trigger-dev-setup.sh +12 -0
  147. package/tests/README.md +45 -0
  148. package/.claude/commands/cost-savings-status.md +0 -34
  149. package/.claude/commands/metrics-summary.md +0 -58
  150. package/claude-assets/agents/cfn-dev-team/dev-ops/monitoring-specialist.md +0 -768
  151. package/claude-assets/agents/custom/test-mcp-access.md +0 -24
  152. package/claude-assets/commands/cost-savings-status.md +0 -34
  153. package/claude-assets/commands/metrics-summary.md +0 -58
  154. package/tests/test-memory-leak-task-mode.sh +0 -435
@@ -0,0 +1,258 @@
1
+ /**
2
+ * Environment Variable Contract Resolver
3
+ *
4
+ * Provides single source of truth for environment variables with mode-specific overrides.
5
+ * Implements Phase 3 of CLI/Trigger.dev collision mitigation strategy.
6
+ *
7
+ * Reference: planning/trigger/CLI_TRIGGER_COLLISION_ANALYSIS.md (Phase 3)
8
+ * Contract: docker/runtime/cfn-runtime.contract.yml
9
+ *
10
+ * Variable Resolution Order (First Set Wins):
11
+ * 1. Mode-specific overrides (if specified in contract)
12
+ * 2. Environment variable with CFN_ prefix (standard)
13
+ * 3. Legacy environment variable (with deprecation warning)
14
+ * 4. Default value from contract
15
+ * 5. Error if no value found and required=true
16
+ *
17
+ * Usage:
18
+ * import { getEnvValue } from './environment-contract';
19
+ *
20
+ * // CLI mode - uses mcp-network
21
+ * const cliRedisHost = getEnvValue('redis_host', 'cli');
22
+ *
23
+ * // Trigger.dev mode - uses trigger-cfn-network
24
+ * const triggerRedisHost = getEnvValue('redis_host', 'trigger');
25
+ *
26
+ * // Environment override takes precedence
27
+ * process.env.CFN_REDIS_HOST = 'custom-redis';
28
+ * const overriddenHost = getEnvValue('redis_host', 'cli'); // Returns 'custom-redis'
29
+ */ import * as fs from 'fs';
30
+ import * as path from 'path';
31
+ import { fileURLToPath } from 'url';
32
+ import * as yaml from 'js-yaml';
33
+ // ESM-compatible __dirname
34
+ const __filename = fileURLToPath(import.meta.url);
35
+ const __dirname = path.dirname(__filename);
36
+ /**
37
+ * Loaded contract cache (lazy-loaded on first use)
38
+ */ let contractCache = null;
39
+ /**
40
+ * Clears the contract cache (for testing purposes)
41
+ * @internal
42
+ */ export function _clearContractCache() {
43
+ contractCache = null;
44
+ }
45
+ /**
46
+ * Loads the environment variable contract from YAML file
47
+ * Cached after first load for performance
48
+ *
49
+ * @returns Contract specification mapping
50
+ * @throws Error if contract file not found or invalid YAML
51
+ */ function loadContract() {
52
+ if (contractCache) {
53
+ return contractCache;
54
+ }
55
+ const projectRoot = process.env.PROJECT_ROOT || path.resolve(__dirname, '../../');
56
+ const contractPath = path.resolve(projectRoot, 'docker/runtime/cfn-runtime.contract.yml');
57
+ if (!fs.existsSync(contractPath)) {
58
+ throw new Error(`Environment contract not found at ${contractPath}. ` + `Ensure docker/runtime/cfn-runtime.contract.yml exists.`);
59
+ }
60
+ const contractYaml = fs.readFileSync(contractPath, 'utf8');
61
+ const contractData = yaml.load(contractYaml);
62
+ // Flatten nested contract structure (e.g., redis.CFN_REDIS_HOST becomes redis_host)
63
+ contractCache = flattenContract(contractData);
64
+ return contractCache;
65
+ }
66
+ /**
67
+ * Flattens nested contract structure into flat key mapping
68
+ * Maps environment variable names (CFN_REDIS_HOST) to their specs
69
+ *
70
+ * @param nested - Nested contract from YAML
71
+ * @returns Flattened mapping with both simple keys and spec metadata
72
+ */ function flattenContract(nested) {
73
+ const flattened = {};
74
+ // Process each top-level category (redis, agent, task, etc.)
75
+ for (const [category, variables] of Object.entries(nested)){
76
+ // Skip metadata fields
77
+ if (category === 'version' || category === 'last_updated') {
78
+ continue;
79
+ }
80
+ if (typeof variables !== 'object' || variables === null) {
81
+ continue;
82
+ }
83
+ // Process each variable in category
84
+ for (const [envVarName, spec] of Object.entries(variables)){
85
+ if (typeof spec === 'object' && spec !== null) {
86
+ // Create a simple key from env var name (e.g., CFN_REDIS_HOST -> redis_host)
87
+ const simpleKey = envVarName.replace(/^CFN_/, '').toLowerCase().replace(/_/g, '_');
88
+ const specWithCfnName = {
89
+ ...spec,
90
+ _cfnVarName: envVarName
91
+ };
92
+ flattened[simpleKey] = specWithCfnName;
93
+ }
94
+ }
95
+ }
96
+ return flattened;
97
+ }
98
+ /**
99
+ * Gets environment value with mode-specific override support
100
+ *
101
+ * Resolution order:
102
+ * 1. Mode-specific override from contract (if defined)
103
+ * 2. CFN_-prefixed environment variable
104
+ * 3. Legacy environment variable (with warning)
105
+ * 4. Default from contract
106
+ * 5. Error if required and no value found
107
+ *
108
+ * @param key - Contract key (e.g., 'redis_host')
109
+ * @param mode - Execution mode ('cli' or 'trigger')
110
+ * @returns Resolved environment variable value as string
111
+ * @throws Error if key not found in contract or required value missing
112
+ */ export function getEnvValue(key, mode) {
113
+ const contract = loadContract();
114
+ const spec = contract[key];
115
+ if (!spec) {
116
+ throw new Error(`Unknown contract key: '${key}'. ` + `Available keys: ${Object.keys(contract).filter((k)=>!k.startsWith('_')).join(', ')}`);
117
+ }
118
+ // Get the CFN variable name for this spec
119
+ const cfnVarName = spec._cfnVarName || 'CFN_VAR';
120
+ // Step 1: Check CFN_ prefixed environment variable (highest priority explicit env var)
121
+ if (process.env[cfnVarName]) {
122
+ return process.env[cfnVarName];
123
+ }
124
+ // Step 2: Check legacy environment variables
125
+ if (spec.legacy_aliases && spec.legacy_aliases.length > 0) {
126
+ for (const legacy of spec.legacy_aliases){
127
+ if (process.env[legacy]) {
128
+ console.warn(`[ENV DEPRECATION] Using legacy environment variable '${legacy}', ` + `migrate to '${cfnVarName}' (see docker/runtime/cfn-runtime.contract.yml)`);
129
+ return process.env[legacy];
130
+ }
131
+ }
132
+ }
133
+ // Step 3: Use mode-specific override if no explicit env var was set
134
+ if (spec.modes?.[mode]?.override !== undefined) {
135
+ return String(spec.modes[mode].override);
136
+ }
137
+ // Step 4: Use default value
138
+ if (spec.default !== null && spec.default !== undefined) {
139
+ return String(spec.default);
140
+ }
141
+ // Step 5: Error if required
142
+ if (spec.required) {
143
+ throw new Error(`Required environment variable '${cfnVarName}' not set. ` + `See docker/runtime/cfn-runtime.contract.yml for configuration.`);
144
+ }
145
+ // No value found and not required - return empty string
146
+ return '';
147
+ }
148
+ /**
149
+ * Gets mode-specific network name from contract
150
+ *
151
+ * @param mode - Execution mode ('cli' or 'trigger')
152
+ * @returns Network name for the mode
153
+ */ export function getNetworkName(mode) {
154
+ const contract = loadContract();
155
+ const spec = contract['network_name'];
156
+ if (!spec) {
157
+ // Fallback to default if not in contract
158
+ return mode === 'cli' ? 'mcp-network' : 'trigger-cfn-network';
159
+ }
160
+ return getEnvValue('network_name', mode);
161
+ }
162
+ /**
163
+ * Gets all environment variables for a specific mode
164
+ * Useful for Docker environment setup
165
+ *
166
+ * @param mode - Execution mode ('cli' or 'trigger')
167
+ * @returns Object with resolved environment variables
168
+ */ export function getAllEnvValues(mode) {
169
+ const contract = loadContract();
170
+ const envVars = {};
171
+ for (const [key, spec] of Object.entries(contract)){
172
+ if (spec && typeof spec === 'object' && 'description' in spec) {
173
+ try {
174
+ const value = getEnvValue(key, mode);
175
+ if (value) {
176
+ envVars[key] = value;
177
+ }
178
+ } catch {
179
+ // Skip variables that can't be resolved (optional)
180
+ }
181
+ }
182
+ }
183
+ return envVars;
184
+ }
185
+ /**
186
+ * Validates an environment variable against contract rules
187
+ *
188
+ * @param key - Contract key
189
+ * @param value - Value to validate
190
+ * @returns Validation result with optional error message
191
+ */ export function validateEnvValue(key, value) {
192
+ const contract = loadContract();
193
+ const spec = contract[key];
194
+ if (!spec) {
195
+ return {
196
+ valid: false,
197
+ error: `Unknown contract key: '${key}'`
198
+ };
199
+ }
200
+ const rules = spec.validation;
201
+ if (!rules) {
202
+ return {
203
+ valid: true
204
+ };
205
+ }
206
+ // Pattern validation
207
+ if (rules.pattern) {
208
+ const regex = new RegExp(rules.pattern);
209
+ if (!regex.test(value)) {
210
+ return {
211
+ valid: false,
212
+ error: `Value '${value}' does not match pattern '${rules.pattern}'`
213
+ };
214
+ }
215
+ }
216
+ // Numeric validations
217
+ if (spec.type === 'integer' || spec.type === 'float') {
218
+ const numValue = spec.type === 'integer' ? parseInt(value, 10) : parseFloat(value);
219
+ if (isNaN(numValue)) {
220
+ return {
221
+ valid: false,
222
+ error: `Value '${value}' is not a valid ${spec.type}`
223
+ };
224
+ }
225
+ if (rules.min !== undefined && numValue < rules.min) {
226
+ return {
227
+ valid: false,
228
+ error: `Value ${numValue} is less than minimum ${rules.min}`
229
+ };
230
+ }
231
+ if (rules.max !== undefined && numValue > rules.max) {
232
+ return {
233
+ valid: false,
234
+ error: `Value ${numValue} is greater than maximum ${rules.max}`
235
+ };
236
+ }
237
+ }
238
+ // Allowed values
239
+ if (rules.allowed_values && !rules.allowed_values.includes(value)) {
240
+ return {
241
+ valid: false,
242
+ error: `Value '${value}' is not in allowed values: ${rules.allowed_values.join(', ')}`
243
+ };
244
+ }
245
+ return {
246
+ valid: true
247
+ };
248
+ }
249
+ /**
250
+ * Exports for barrel import
251
+ */ export default {
252
+ getEnvValue,
253
+ getNetworkName,
254
+ getAllEnvValues,
255
+ validateEnvValue
256
+ };
257
+
258
+ //# sourceMappingURL=environment-contract.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/lib/environment-contract.ts"],"sourcesContent":["/**\r\n * Environment Variable Contract Resolver\r\n *\r\n * Provides single source of truth for environment variables with mode-specific overrides.\r\n * Implements Phase 3 of CLI/Trigger.dev collision mitigation strategy.\r\n *\r\n * Reference: planning/trigger/CLI_TRIGGER_COLLISION_ANALYSIS.md (Phase 3)\r\n * Contract: docker/runtime/cfn-runtime.contract.yml\r\n *\r\n * Variable Resolution Order (First Set Wins):\r\n * 1. Mode-specific overrides (if specified in contract)\r\n * 2. Environment variable with CFN_ prefix (standard)\r\n * 3. Legacy environment variable (with deprecation warning)\r\n * 4. Default value from contract\r\n * 5. Error if no value found and required=true\r\n *\r\n * Usage:\r\n * import { getEnvValue } from './environment-contract';\r\n *\r\n * // CLI mode - uses mcp-network\r\n * const cliRedisHost = getEnvValue('redis_host', 'cli');\r\n *\r\n * // Trigger.dev mode - uses trigger-cfn-network\r\n * const triggerRedisHost = getEnvValue('redis_host', 'trigger');\r\n *\r\n * // Environment override takes precedence\r\n * process.env.CFN_REDIS_HOST = 'custom-redis';\r\n * const overriddenHost = getEnvValue('redis_host', 'cli'); // Returns 'custom-redis'\r\n */\r\n\r\nimport * as fs from 'fs';\r\nimport * as path from 'path';\r\nimport { fileURLToPath } from 'url';\r\nimport * as yaml from 'js-yaml';\r\n\r\n// ESM-compatible __dirname\r\nconst __filename = fileURLToPath(import.meta.url);\r\nconst __dirname = path.dirname(__filename);\r\n\r\n/**\r\n * Validation constraints for contract values\r\n */\r\ninterface ValidationRules {\r\n pattern?: string;\r\n min?: number;\r\n max?: number;\r\n allowed_values?: string[];\r\n description?: string;\r\n}\r\n\r\n/**\r\n * Mode-specific overrides in contract\r\n */\r\ninterface ModeOverride {\r\n override?: string | number;\r\n network?: string;\r\n}\r\n\r\n/**\r\n * Contract specification for a single environment variable\r\n */\r\ninterface ContractSpec {\r\n description: string;\r\n default: string | number | null;\r\n type: 'string' | 'integer' | 'boolean' | 'float';\r\n scope: string[];\r\n legacy_aliases?: string[];\r\n required?: boolean;\r\n required_in_production?: boolean;\r\n example?: string;\r\n security_notes?: string;\r\n validation?: ValidationRules;\r\n mode_defaults?: Record<string, string | number>;\r\n modes?: {\r\n cli?: ModeOverride;\r\n trigger?: ModeOverride;\r\n };\r\n}\r\n\r\n/**\r\n * Loaded contract cache (lazy-loaded on first use)\r\n */\r\nlet contractCache: Record<string, ContractSpec & { _cfnVarName?: string }> | null = null;\r\n\r\n/**\r\n * Clears the contract cache (for testing purposes)\r\n * @internal\r\n */\r\nexport function _clearContractCache(): void {\r\n contractCache = null;\r\n}\r\n\r\n/**\r\n * Loads the environment variable contract from YAML file\r\n * Cached after first load for performance\r\n *\r\n * @returns Contract specification mapping\r\n * @throws Error if contract file not found or invalid YAML\r\n */\r\nfunction loadContract(): Record<string, ContractSpec> {\r\n if (contractCache) {\r\n return contractCache;\r\n }\r\n\r\n const projectRoot = process.env.PROJECT_ROOT || path.resolve(__dirname, '../../');\r\n const contractPath = path.resolve(projectRoot, 'docker/runtime/cfn-runtime.contract.yml');\r\n\r\n if (!fs.existsSync(contractPath)) {\r\n throw new Error(\r\n `Environment contract not found at ${contractPath}. ` +\r\n `Ensure docker/runtime/cfn-runtime.contract.yml exists.`\r\n );\r\n }\r\n\r\n const contractYaml = fs.readFileSync(contractPath, 'utf8');\r\n const contractData = yaml.load(contractYaml) as Record<string, any>;\r\n\r\n // Flatten nested contract structure (e.g., redis.CFN_REDIS_HOST becomes redis_host)\r\n contractCache = flattenContract(contractData);\r\n\r\n return contractCache;\r\n}\r\n\r\n/**\r\n * Flattens nested contract structure into flat key mapping\r\n * Maps environment variable names (CFN_REDIS_HOST) to their specs\r\n *\r\n * @param nested - Nested contract from YAML\r\n * @returns Flattened mapping with both simple keys and spec metadata\r\n */\r\nfunction flattenContract(nested: Record<string, any>): Record<string, ContractSpec & { _cfnVarName?: string }> {\r\n const flattened: Record<string, ContractSpec & { _cfnVarName?: string }> = {};\r\n\r\n // Process each top-level category (redis, agent, task, etc.)\r\n for (const [category, variables] of Object.entries(nested)) {\r\n // Skip metadata fields\r\n if (category === 'version' || category === 'last_updated') {\r\n continue;\r\n }\r\n\r\n if (typeof variables !== 'object' || variables === null) {\r\n continue;\r\n }\r\n\r\n // Process each variable in category\r\n for (const [envVarName, spec] of Object.entries(variables)) {\r\n if (typeof spec === 'object' && spec !== null) {\r\n // Create a simple key from env var name (e.g., CFN_REDIS_HOST -> redis_host)\r\n const simpleKey = envVarName\r\n .replace(/^CFN_/, '')\r\n .toLowerCase()\r\n .replace(/_/g, '_');\r\n\r\n const specWithCfnName = {\r\n ...(spec as ContractSpec),\r\n _cfnVarName: envVarName, // Store the CFN variable name for later lookup\r\n };\r\n\r\n flattened[simpleKey] = specWithCfnName;\r\n }\r\n }\r\n }\r\n\r\n return flattened;\r\n}\r\n\r\n/**\r\n * Gets environment value with mode-specific override support\r\n *\r\n * Resolution order:\r\n * 1. Mode-specific override from contract (if defined)\r\n * 2. CFN_-prefixed environment variable\r\n * 3. Legacy environment variable (with warning)\r\n * 4. Default from contract\r\n * 5. Error if required and no value found\r\n *\r\n * @param key - Contract key (e.g., 'redis_host')\r\n * @param mode - Execution mode ('cli' or 'trigger')\r\n * @returns Resolved environment variable value as string\r\n * @throws Error if key not found in contract or required value missing\r\n */\r\nexport function getEnvValue(key: string, mode: 'cli' | 'trigger'): string {\r\n const contract = loadContract();\r\n const spec = contract[key] as (ContractSpec & { _cfnVarName?: string }) | undefined;\r\n\r\n if (!spec) {\r\n throw new Error(\r\n `Unknown contract key: '${key}'. ` +\r\n `Available keys: ${Object.keys(contract).filter(k => !k.startsWith('_')).join(', ')}`\r\n );\r\n }\r\n\r\n // Get the CFN variable name for this spec\r\n const cfnVarName = spec._cfnVarName || 'CFN_VAR';\r\n\r\n // Step 1: Check CFN_ prefixed environment variable (highest priority explicit env var)\r\n if (process.env[cfnVarName]) {\r\n return process.env[cfnVarName];\r\n }\r\n\r\n // Step 2: Check legacy environment variables\r\n if (spec.legacy_aliases && spec.legacy_aliases.length > 0) {\r\n for (const legacy of spec.legacy_aliases) {\r\n if (process.env[legacy]) {\r\n console.warn(\r\n `[ENV DEPRECATION] Using legacy environment variable '${legacy}', ` +\r\n `migrate to '${cfnVarName}' (see docker/runtime/cfn-runtime.contract.yml)`\r\n );\r\n return process.env[legacy];\r\n }\r\n }\r\n }\r\n\r\n // Step 3: Use mode-specific override if no explicit env var was set\r\n if (spec.modes?.[mode]?.override !== undefined) {\r\n return String(spec.modes[mode].override);\r\n }\r\n\r\n // Step 4: Use default value\r\n if (spec.default !== null && spec.default !== undefined) {\r\n return String(spec.default);\r\n }\r\n\r\n // Step 5: Error if required\r\n if (spec.required) {\r\n throw new Error(\r\n `Required environment variable '${cfnVarName}' not set. ` +\r\n `See docker/runtime/cfn-runtime.contract.yml for configuration.`\r\n );\r\n }\r\n\r\n // No value found and not required - return empty string\r\n return '';\r\n}\r\n\r\n/**\r\n * Gets mode-specific network name from contract\r\n *\r\n * @param mode - Execution mode ('cli' or 'trigger')\r\n * @returns Network name for the mode\r\n */\r\nexport function getNetworkName(mode: 'cli' | 'trigger'): string {\r\n const contract = loadContract();\r\n const spec = contract['network_name'];\r\n\r\n if (!spec) {\r\n // Fallback to default if not in contract\r\n return mode === 'cli' ? 'mcp-network' : 'trigger-cfn-network';\r\n }\r\n\r\n return getEnvValue('network_name', mode);\r\n}\r\n\r\n/**\r\n * Gets all environment variables for a specific mode\r\n * Useful for Docker environment setup\r\n *\r\n * @param mode - Execution mode ('cli' or 'trigger')\r\n * @returns Object with resolved environment variables\r\n */\r\nexport function getAllEnvValues(mode: 'cli' | 'trigger'): Record<string, string> {\r\n const contract = loadContract();\r\n const envVars: Record<string, string> = {};\r\n\r\n for (const [key, spec] of Object.entries(contract)) {\r\n if (spec && typeof spec === 'object' && 'description' in spec) {\r\n try {\r\n const value = getEnvValue(key, mode);\r\n if (value) {\r\n envVars[key] = value;\r\n }\r\n } catch {\r\n // Skip variables that can't be resolved (optional)\r\n }\r\n }\r\n }\r\n\r\n return envVars;\r\n}\r\n\r\n/**\r\n * Validates an environment variable against contract rules\r\n *\r\n * @param key - Contract key\r\n * @param value - Value to validate\r\n * @returns Validation result with optional error message\r\n */\r\nexport function validateEnvValue(key: string, value: string): { valid: boolean; error?: string } {\r\n const contract = loadContract();\r\n const spec = contract[key];\r\n\r\n if (!spec) {\r\n return { valid: false, error: `Unknown contract key: '${key}'` };\r\n }\r\n\r\n const rules = spec.validation;\r\n if (!rules) {\r\n return { valid: true };\r\n }\r\n\r\n // Pattern validation\r\n if (rules.pattern) {\r\n const regex = new RegExp(rules.pattern);\r\n if (!regex.test(value)) {\r\n return {\r\n valid: false,\r\n error: `Value '${value}' does not match pattern '${rules.pattern}'`,\r\n };\r\n }\r\n }\r\n\r\n // Numeric validations\r\n if (spec.type === 'integer' || spec.type === 'float') {\r\n const numValue = spec.type === 'integer' ? parseInt(value, 10) : parseFloat(value);\r\n\r\n if (isNaN(numValue)) {\r\n return {\r\n valid: false,\r\n error: `Value '${value}' is not a valid ${spec.type}`,\r\n };\r\n }\r\n\r\n if (rules.min !== undefined && numValue < rules.min) {\r\n return {\r\n valid: false,\r\n error: `Value ${numValue} is less than minimum ${rules.min}`,\r\n };\r\n }\r\n\r\n if (rules.max !== undefined && numValue > rules.max) {\r\n return {\r\n valid: false,\r\n error: `Value ${numValue} is greater than maximum ${rules.max}`,\r\n };\r\n }\r\n }\r\n\r\n // Allowed values\r\n if (rules.allowed_values && !rules.allowed_values.includes(value)) {\r\n return {\r\n valid: false,\r\n error: `Value '${value}' is not in allowed values: ${rules.allowed_values.join(', ')}`,\r\n };\r\n }\r\n\r\n return { valid: true };\r\n}\r\n\r\n\r\n/**\r\n * Exports for barrel import\r\n */\r\nexport default {\r\n getEnvValue,\r\n getNetworkName,\r\n getAllEnvValues,\r\n validateEnvValue,\r\n};\r\n"],"names":["fs","path","fileURLToPath","yaml","__filename","url","__dirname","dirname","contractCache","_clearContractCache","loadContract","projectRoot","process","env","PROJECT_ROOT","resolve","contractPath","existsSync","Error","contractYaml","readFileSync","contractData","load","flattenContract","nested","flattened","category","variables","Object","entries","envVarName","spec","simpleKey","replace","toLowerCase","specWithCfnName","_cfnVarName","getEnvValue","key","mode","contract","keys","filter","k","startsWith","join","cfnVarName","legacy_aliases","length","legacy","console","warn","modes","override","undefined","String","default","required","getNetworkName","getAllEnvValues","envVars","value","validateEnvValue","valid","error","rules","validation","pattern","regex","RegExp","test","type","numValue","parseInt","parseFloat","isNaN","min","max","allowed_values","includes"],"mappings":"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"}
@@ -0,0 +1,388 @@
1
+ /**
2
+ * Query Optimizer
3
+ *
4
+ * Implements database query optimizations including:
5
+ * - Index management for agents table
6
+ * - Materialized views for cost aggregation
7
+ * - Query pattern optimization
8
+ * - Expected: 10-20x query speedup
9
+ *
10
+ * Features:
11
+ * - Automated index creation
12
+ * - Materialized view management
13
+ * - Query rewriting for optimal execution
14
+ * - Performance monitoring
15
+ */ export class QueryOptimizer {
16
+ pool;
17
+ refreshInterval;
18
+ refreshTimer = null;
19
+ constructor(config){
20
+ this.pool = config.pool;
21
+ this.refreshInterval = config.refreshInterval || 3600000; // 1 hour default
22
+ }
23
+ /**
24
+ * Initialize all query optimizations
25
+ */ async initialize() {
26
+ console.log('Initializing query optimizer...');
27
+ await this.createIndexes();
28
+ await this.createMaterializedViews();
29
+ await this.startMaterializedViewRefresh();
30
+ console.log('Query optimizer initialized successfully');
31
+ }
32
+ /**
33
+ * Create indexes on agents table for performance
34
+ * Indexes: team_id, status, spawned_at
35
+ */ async createIndexes() {
36
+ const indexes = [
37
+ {
38
+ name: 'idx_agents_team_id',
39
+ table: 'agents',
40
+ columns: [
41
+ 'team_id'
42
+ ],
43
+ description: 'Index for team-based queries'
44
+ },
45
+ {
46
+ name: 'idx_agents_status',
47
+ table: 'agents',
48
+ columns: [
49
+ 'status'
50
+ ],
51
+ description: 'Index for status filtering'
52
+ },
53
+ {
54
+ name: 'idx_agents_spawned_at',
55
+ table: 'agents',
56
+ columns: [
57
+ 'spawned_at'
58
+ ],
59
+ description: 'Index for time-based queries'
60
+ },
61
+ {
62
+ name: 'idx_agents_team_status',
63
+ table: 'agents',
64
+ columns: [
65
+ 'team_id',
66
+ 'status'
67
+ ],
68
+ description: 'Composite index for team + status queries'
69
+ },
70
+ {
71
+ name: 'idx_agents_status_spawned',
72
+ table: 'agents',
73
+ columns: [
74
+ 'status',
75
+ 'spawned_at'
76
+ ],
77
+ description: 'Composite index for status + time queries'
78
+ },
79
+ {
80
+ name: 'idx_agents_cost_query',
81
+ table: 'agents',
82
+ columns: [
83
+ 'team_id',
84
+ 'spawned_at',
85
+ 'status'
86
+ ],
87
+ description: 'Composite index for cost aggregation queries'
88
+ }
89
+ ];
90
+ const client = await this.pool.connect();
91
+ try {
92
+ for (const index of indexes){
93
+ const query = `
94
+ CREATE INDEX IF NOT EXISTS ${index.name}
95
+ ON ${index.table} (${index.columns.join(', ')})
96
+ `;
97
+ await client.query(query);
98
+ console.log(`Created index: ${index.name} - ${index.description}`);
99
+ }
100
+ } finally{
101
+ client.release();
102
+ }
103
+ }
104
+ /**
105
+ * Create materialized views for cost aggregation queries
106
+ */ async createMaterializedViews() {
107
+ const client = await this.pool.connect();
108
+ try {
109
+ // Drop existing views if they exist
110
+ await client.query('DROP MATERIALIZED VIEW IF EXISTS mv_cost_by_team CASCADE');
111
+ await client.query('DROP MATERIALIZED VIEW IF EXISTS mv_cost_by_agent_type CASCADE');
112
+ await client.query('DROP MATERIALIZED VIEW IF EXISTS mv_daily_cost_summary CASCADE');
113
+ // Create materialized view for cost by team
114
+ await client.query(`
115
+ CREATE MATERIALIZED VIEW mv_cost_by_team AS
116
+ SELECT
117
+ team_id,
118
+ COUNT(*) as agent_count,
119
+ SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) as completed_count,
120
+ SUM(CASE WHEN status = 'failed' THEN 1 ELSE 0 END) as failed_count,
121
+ AVG(confidence) as avg_confidence,
122
+ SUM(COALESCE(metadata::json->>'cost', '0')::numeric) as total_cost,
123
+ MIN(spawned_at) as first_spawn,
124
+ MAX(spawned_at) as last_spawn
125
+ FROM agents
126
+ WHERE team_id IS NOT NULL
127
+ GROUP BY team_id
128
+ `);
129
+ console.log('Created materialized view: mv_cost_by_team');
130
+ // Create materialized view for cost by agent type
131
+ await client.query(`
132
+ CREATE MATERIALIZED VIEW mv_cost_by_agent_type AS
133
+ SELECT
134
+ type as agent_type,
135
+ COUNT(*) as agent_count,
136
+ SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) as completed_count,
137
+ SUM(CASE WHEN status = 'failed' THEN 1 ELSE 0 END) as failed_count,
138
+ AVG(confidence) as avg_confidence,
139
+ SUM(COALESCE(metadata::json->>'cost', '0')::numeric) as total_cost,
140
+ AVG(EXTRACT(EPOCH FROM (completed_at - spawned_at))) as avg_duration_seconds
141
+ FROM agents
142
+ WHERE type IS NOT NULL
143
+ GROUP BY type
144
+ `);
145
+ console.log('Created materialized view: mv_cost_by_agent_type');
146
+ // Create materialized view for daily cost summary
147
+ await client.query(`
148
+ CREATE MATERIALIZED VIEW mv_daily_cost_summary AS
149
+ SELECT
150
+ DATE(spawned_at) as date,
151
+ COUNT(*) as total_agents,
152
+ SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) as completed_count,
153
+ SUM(CASE WHEN status = 'failed' THEN 1 ELSE 0 END) as failed_count,
154
+ SUM(COALESCE(metadata::json->>'cost', '0')::numeric) as total_cost,
155
+ AVG(confidence) as avg_confidence
156
+ FROM agents
157
+ WHERE spawned_at IS NOT NULL
158
+ GROUP BY DATE(spawned_at)
159
+ ORDER BY date DESC
160
+ `);
161
+ console.log('Created materialized view: mv_daily_cost_summary');
162
+ // Create UNIQUE indexes on materialized views (required for CONCURRENT refresh)
163
+ await client.query('CREATE UNIQUE INDEX idx_mv_cost_by_team_team_id ON mv_cost_by_team (team_id)');
164
+ await client.query('CREATE UNIQUE INDEX idx_mv_cost_by_agent_type_type ON mv_cost_by_agent_type (agent_type)');
165
+ await client.query('CREATE UNIQUE INDEX idx_mv_daily_cost_summary_date ON mv_daily_cost_summary (date)');
166
+ console.log('Created UNIQUE indexes on materialized views for concurrent refresh');
167
+ } finally{
168
+ client.release();
169
+ }
170
+ }
171
+ /**
172
+ * Refresh materialized views
173
+ */ async refreshMaterializedViews() {
174
+ const client = await this.pool.connect();
175
+ try {
176
+ console.log('Refreshing materialized views...');
177
+ await client.query('REFRESH MATERIALIZED VIEW CONCURRENTLY mv_cost_by_team');
178
+ await client.query('REFRESH MATERIALIZED VIEW CONCURRENTLY mv_cost_by_agent_type');
179
+ await client.query('REFRESH MATERIALIZED VIEW CONCURRENTLY mv_daily_cost_summary');
180
+ console.log('Materialized views refreshed successfully');
181
+ } catch (err) {
182
+ console.error('Error refreshing materialized views:', err);
183
+ throw err;
184
+ } finally{
185
+ client.release();
186
+ }
187
+ }
188
+ /**
189
+ * Start automatic materialized view refresh
190
+ */ startMaterializedViewRefresh() {
191
+ if (this.refreshTimer) {
192
+ clearInterval(this.refreshTimer);
193
+ }
194
+ // Initial refresh
195
+ this.refreshMaterializedViews().catch(console.error);
196
+ // Schedule periodic refresh
197
+ this.refreshTimer = setInterval(()=>{
198
+ this.refreshMaterializedViews().catch(console.error);
199
+ }, this.refreshInterval);
200
+ console.log(`Started materialized view auto-refresh (interval: ${this.refreshInterval / 1000}s)`);
201
+ }
202
+ /**
203
+ * Stop automatic materialized view refresh
204
+ */ stopMaterializedViewRefresh() {
205
+ if (this.refreshTimer) {
206
+ clearInterval(this.refreshTimer);
207
+ this.refreshTimer = null;
208
+ console.log('Stopped materialized view auto-refresh');
209
+ }
210
+ }
211
+ /**
212
+ * Optimized query: Get cost by team
213
+ */ async getCostByTeam(teamId) {
214
+ const client = await this.pool.connect();
215
+ try {
216
+ let query = 'SELECT * FROM mv_cost_by_team';
217
+ const params = [];
218
+ if (teamId) {
219
+ query += ' WHERE team_id = $1';
220
+ params.push(teamId);
221
+ }
222
+ query += ' ORDER BY total_cost DESC';
223
+ const result = await client.query(query, params);
224
+ return result.rows;
225
+ } finally{
226
+ client.release();
227
+ }
228
+ }
229
+ /**
230
+ * Optimized query: Get cost by agent type
231
+ */ async getCostByAgentType(agentType) {
232
+ const client = await this.pool.connect();
233
+ try {
234
+ let query = 'SELECT * FROM mv_cost_by_agent_type';
235
+ const params = [];
236
+ if (agentType) {
237
+ query += ' WHERE agent_type = $1';
238
+ params.push(agentType);
239
+ }
240
+ query += ' ORDER BY total_cost DESC';
241
+ const result = await client.query(query, params);
242
+ return result.rows;
243
+ } finally{
244
+ client.release();
245
+ }
246
+ }
247
+ /**
248
+ * Optimized query: Get daily cost summary
249
+ */ async getDailyCostSummary(startDate, endDate) {
250
+ const client = await this.pool.connect();
251
+ try {
252
+ let query = 'SELECT * FROM mv_daily_cost_summary';
253
+ const params = [];
254
+ const conditions = [];
255
+ if (startDate) {
256
+ params.push(startDate);
257
+ conditions.push(`date >= $${params.length}`);
258
+ }
259
+ if (endDate) {
260
+ params.push(endDate);
261
+ conditions.push(`date <= $${params.length}`);
262
+ }
263
+ if (conditions.length > 0) {
264
+ query += ' WHERE ' + conditions.join(' AND ');
265
+ }
266
+ query += ' ORDER BY date DESC';
267
+ const result = await client.query(query, params);
268
+ return result.rows;
269
+ } finally{
270
+ client.release();
271
+ }
272
+ }
273
+ /**
274
+ * Optimized query: Get agents by team and status
275
+ * Uses composite index idx_agents_team_status
276
+ */ async getAgentsByTeamAndStatus(teamId, status) {
277
+ const client = await this.pool.connect();
278
+ try {
279
+ const query = `
280
+ SELECT *
281
+ FROM agents
282
+ WHERE team_id = $1 AND status = $2
283
+ ORDER BY spawned_at DESC
284
+ `;
285
+ const result = await client.query(query, [
286
+ teamId,
287
+ status
288
+ ]);
289
+ return result.rows;
290
+ } finally{
291
+ client.release();
292
+ }
293
+ }
294
+ /**
295
+ * Optimized query: Get agents by status and time range
296
+ * Uses composite index idx_agents_status_spawned
297
+ */ async getAgentsByStatusAndTimeRange(status, startDate, endDate) {
298
+ const client = await this.pool.connect();
299
+ try {
300
+ const query = `
301
+ SELECT *
302
+ FROM agents
303
+ WHERE status = $1
304
+ AND spawned_at >= $2
305
+ AND spawned_at <= $3
306
+ ORDER BY spawned_at DESC
307
+ `;
308
+ const result = await client.query(query, [
309
+ status,
310
+ startDate,
311
+ endDate
312
+ ]);
313
+ return result.rows;
314
+ } finally{
315
+ client.release();
316
+ }
317
+ }
318
+ /**
319
+ * Analyze query performance
320
+ */ async analyzeQuery(query, params) {
321
+ const client = await this.pool.connect();
322
+ try {
323
+ const explainQuery = `EXPLAIN (ANALYZE, BUFFERS, FORMAT JSON) ${query}`;
324
+ const result = await client.query(explainQuery, params);
325
+ return result.rows[0]['QUERY PLAN'][0];
326
+ } finally{
327
+ client.release();
328
+ }
329
+ }
330
+ /**
331
+ * Get index usage statistics
332
+ */ async getIndexUsageStats() {
333
+ const client = await this.pool.connect();
334
+ try {
335
+ const query = `
336
+ SELECT
337
+ schemaname,
338
+ tablename,
339
+ indexname,
340
+ idx_scan as index_scans,
341
+ idx_tup_read as tuples_read,
342
+ idx_tup_fetch as tuples_fetched
343
+ FROM pg_stat_user_indexes
344
+ WHERE schemaname = 'public'
345
+ ORDER BY idx_scan DESC
346
+ `;
347
+ const result = await client.query(query);
348
+ return result.rows;
349
+ } finally{
350
+ client.release();
351
+ }
352
+ }
353
+ /**
354
+ * Shutdown query optimizer
355
+ */ async shutdown() {
356
+ this.stopMaterializedViewRefresh();
357
+ console.log('Query optimizer shutdown complete');
358
+ }
359
+ }
360
+ // Singleton instance
361
+ let queryOptimizerInstance = null;
362
+ /**
363
+ * Initialize singleton query optimizer
364
+ */ export async function initQueryOptimizer(config) {
365
+ if (!queryOptimizerInstance) {
366
+ queryOptimizerInstance = new QueryOptimizer(config);
367
+ await queryOptimizerInstance.initialize();
368
+ }
369
+ return queryOptimizerInstance;
370
+ }
371
+ /**
372
+ * Get singleton query optimizer instance
373
+ */ export function getQueryOptimizer() {
374
+ if (!queryOptimizerInstance) {
375
+ throw new Error('Query optimizer not initialized. Call initQueryOptimizer first.');
376
+ }
377
+ return queryOptimizerInstance;
378
+ }
379
+ /**
380
+ * Shutdown singleton query optimizer
381
+ */ export async function shutdownQueryOptimizer() {
382
+ if (queryOptimizerInstance) {
383
+ await queryOptimizerInstance.shutdown();
384
+ queryOptimizerInstance = null;
385
+ }
386
+ }
387
+
388
+ //# sourceMappingURL=query-optimizer.js.map