aios-core 4.0.4 → 4.2.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 (107) hide show
  1. package/.aios-core/cli/commands/migrate/analyze.js +6 -6
  2. package/.aios-core/cli/commands/migrate/backup.js +2 -2
  3. package/.aios-core/cli/commands/migrate/execute.js +4 -4
  4. package/.aios-core/cli/commands/migrate/index.js +5 -5
  5. package/.aios-core/cli/commands/migrate/rollback.js +6 -6
  6. package/.aios-core/cli/commands/migrate/update-imports.js +2 -2
  7. package/.aios-core/cli/commands/migrate/validate.js +2 -2
  8. package/.aios-core/cli/commands/pro/index.js +52 -0
  9. package/.aios-core/cli/index.js +1 -1
  10. package/.aios-core/core/ids/registry-updater.js +29 -3
  11. package/.aios-core/core/migration/migration-config.yaml +2 -2
  12. package/.aios-core/core/migration/module-mapping.yaml +2 -2
  13. package/.aios-core/core/registry/README.md +2 -2
  14. package/.aios-core/core/synapse/context/context-builder.js +34 -0
  15. package/.aios-core/core/synapse/diagnostics/collectors/consistency-collector.js +168 -0
  16. package/.aios-core/core/synapse/diagnostics/collectors/hook-collector.js +129 -0
  17. package/.aios-core/core/synapse/diagnostics/collectors/manifest-collector.js +82 -0
  18. package/.aios-core/core/synapse/diagnostics/collectors/output-analyzer.js +134 -0
  19. package/.aios-core/core/synapse/diagnostics/collectors/pipeline-collector.js +75 -0
  20. package/.aios-core/core/synapse/diagnostics/collectors/quality-collector.js +252 -0
  21. package/.aios-core/core/synapse/diagnostics/collectors/relevance-matrix.js +174 -0
  22. package/.aios-core/core/synapse/diagnostics/collectors/safe-read-json.js +31 -0
  23. package/.aios-core/core/synapse/diagnostics/collectors/session-collector.js +102 -0
  24. package/.aios-core/core/synapse/diagnostics/collectors/timing-collector.js +126 -0
  25. package/.aios-core/core/synapse/diagnostics/collectors/uap-collector.js +83 -0
  26. package/.aios-core/core/synapse/diagnostics/report-formatter.js +484 -0
  27. package/.aios-core/core/synapse/diagnostics/synapse-diagnostics.js +95 -0
  28. package/.aios-core/core/synapse/engine.js +73 -20
  29. package/.aios-core/core/synapse/runtime/hook-runtime.js +60 -0
  30. package/.aios-core/core-config.yaml +6 -0
  31. package/.aios-core/data/agent-config-requirements.yaml +2 -2
  32. package/.aios-core/data/aios-kb.md +4 -4
  33. package/.aios-core/data/entity-registry.yaml +210 -10
  34. package/.aios-core/data/registry-update-log.jsonl +52 -0
  35. package/.aios-core/development/agents/architect.md +10 -10
  36. package/.aios-core/development/agents/devops.md +93 -50
  37. package/.aios-core/development/agents/qa.md +94 -40
  38. package/.aios-core/development/agents/ux-design-expert.md +25 -25
  39. package/.aios-core/development/scripts/activation-runtime.js +63 -0
  40. package/.aios-core/development/scripts/generate-greeting.js +9 -8
  41. package/.aios-core/development/scripts/unified-activation-pipeline.js +102 -2
  42. package/.aios-core/development/tasks/{db-expansion-pack-integration.md → db-squad-integration.md} +5 -5
  43. package/.aios-core/development/tasks/{integrate-expansion-pack.md → integrate-squad.md} +2 -2
  44. package/.aios-core/development/tasks/next.md +3 -3
  45. package/.aios-core/development/tasks/pr-automation.md +2 -2
  46. package/.aios-core/development/tasks/publish-npm.md +257 -0
  47. package/.aios-core/development/tasks/release-management.md +4 -4
  48. package/.aios-core/development/tasks/setup-github.md +1 -1
  49. package/.aios-core/development/tasks/squad-creator-migrate.md +1 -1
  50. package/.aios-core/development/tasks/squad-creator-sync-ide-command.md +14 -14
  51. package/.aios-core/development/tasks/update-aios.md +1 -1
  52. package/.aios-core/development/tasks/validate-next-story.md +99 -2
  53. package/.aios-core/docs/standards/AIOS-COLOR-PALETTE-QUICK-REFERENCE.md +1 -1
  54. package/.aios-core/docs/standards/AIOS-COLOR-PALETTE-V2.1.md +5 -5
  55. package/.aios-core/docs/standards/AIOS-LIVRO-DE-OURO-V2.1-COMPLETE.md +21 -21
  56. package/.aios-core/docs/standards/AIOS-LIVRO-DE-OURO-V2.2-SUMMARY.md +25 -25
  57. package/.aios-core/docs/standards/OPEN-SOURCE-VS-SERVICE-DIFFERENCES.md +4 -4
  58. package/.aios-core/docs/standards/QUALITY-GATES-SPECIFICATION.md +3 -3
  59. package/.aios-core/docs/standards/STANDARDS-INDEX.md +13 -13
  60. package/.aios-core/docs/standards/STORY-TEMPLATE-V2-SPECIFICATION.md +1 -1
  61. package/.aios-core/framework-config.yaml +4 -0
  62. package/.aios-core/infrastructure/scripts/codex-skills-sync/index.js +182 -0
  63. package/.aios-core/infrastructure/scripts/codex-skills-sync/validate.js +172 -0
  64. package/.aios-core/infrastructure/scripts/ide-sync/README.md +14 -0
  65. package/.aios-core/infrastructure/scripts/ide-sync/index.js +6 -0
  66. package/.aios-core/infrastructure/scripts/tool-resolver.js +4 -4
  67. package/.aios-core/infrastructure/scripts/validate-paths.js +142 -0
  68. package/.aios-core/infrastructure/templates/aios-sync.yaml.template +11 -11
  69. package/.aios-core/infrastructure/templates/github-workflows/README.md +1 -1
  70. package/.aios-core/install-manifest.yaml +193 -109
  71. package/.aios-core/local-config.yaml.template +2 -0
  72. package/.aios-core/manifests/agents.csv +29 -1
  73. package/.aios-core/manifests/tasks.csv +80 -3
  74. package/.aios-core/product/README.md +2 -2
  75. package/.aios-core/product/data/integration-patterns.md +1 -1
  76. package/.aios-core/product/templates/ide-rules/cline-rules.md +1 -1
  77. package/.aios-core/product/templates/ide-rules/codex-rules.md +65 -0
  78. package/.aios-core/product/templates/ide-rules/copilot-rules.md +1 -1
  79. package/.aios-core/product/templates/ide-rules/roo-rules.md +1 -1
  80. package/.aios-core/user-guide.md +15 -14
  81. package/.aios-core/workflow-intelligence/engine/output-formatter.js +1 -1
  82. package/.claude/hooks/synapse-engine.js +9 -20
  83. package/README.md +14 -7
  84. package/bin/aios-init.js +255 -184
  85. package/bin/aios-minimal.js +2 -2
  86. package/bin/aios.js +4 -4
  87. package/package.json +6 -1
  88. package/packages/aios-pro-cli/bin/aios-pro.js +75 -2
  89. package/packages/aios-pro-cli/package.json +5 -1
  90. package/packages/aios-pro-cli/src/recover.js +100 -0
  91. package/packages/installer/src/__tests__/performance-benchmark.js +382 -0
  92. package/packages/installer/src/config/ide-configs.js +12 -1
  93. package/packages/installer/src/config/templates/core-config-template.js +2 -2
  94. package/packages/installer/src/installer/aios-core-installer.js +2 -2
  95. package/packages/installer/src/installer/file-hasher.js +97 -0
  96. package/packages/installer/src/installer/post-install-validator.js +41 -1
  97. package/packages/installer/src/pro/pro-scaffolder.js +335 -0
  98. package/packages/installer/src/utils/aios-colors.js +2 -2
  99. package/packages/installer/src/wizard/feedback.js +1 -1
  100. package/packages/installer/src/wizard/ide-config-generator.js +2 -2
  101. package/packages/installer/src/wizard/index.js +58 -19
  102. package/packages/installer/src/wizard/pro-setup.js +931 -0
  103. package/packages/installer/src/wizard/questions.js +20 -14
  104. package/packages/installer/src/wizard/validators.js +1 -1
  105. package/scripts/code-intel-health-check.js +343 -0
  106. package/scripts/package-synapse.js +323 -0
  107. package/scripts/validate-package-completeness.js +317 -0
@@ -186,26 +186,30 @@ function getEnvironmentQuestions() {
186
186
  }
187
187
 
188
188
  /**
189
- * Get Expansion Pack selection questions
189
+ * Get Squad selection questions
190
190
  *
191
- * Available expansion packs for v2.1:
192
- * - expansion-creator: Tools to create custom expansion packs
191
+ * Available squads for v4.0:
192
+ * - squad-creator: Tools to create custom squads
193
193
  * - etl: ETL pipeline for knowledge base creation
194
194
  *
195
+ * Note: This function is currently DISABLED. Squad selection is handled
196
+ * directly in aios-init.js using the squads/ directory.
197
+ *
195
198
  * @returns {Object[]} Array of inquirer question objects
199
+ * @deprecated Use squads/ directory directly in aios-init.js
196
200
  */
197
- function getExpansionPackQuestions() {
201
+ function getSquadQuestions() {
198
202
  return [
199
203
  {
200
204
  type: 'checkbox',
201
- name: 'selectedExpansionPacks',
202
- message: colors.primary('Select Expansion Packs to install (optional):'),
205
+ name: 'selectedSquads',
206
+ message: colors.primary('Select Squads to install (optional):'),
203
207
  choices: [
204
208
  {
205
209
  name:
206
- colors.highlight('expansion-creator') +
207
- colors.dim(' - Tools to create custom expansion packs'),
208
- value: 'expansion-creator',
210
+ colors.highlight('squad-creator') +
211
+ colors.dim(' - Tools to create custom squads'),
212
+ value: 'squad-creator',
209
213
  checked: false,
210
214
  },
211
215
  {
@@ -215,7 +219,7 @@ function getExpansionPackQuestions() {
215
219
  },
216
220
  ],
217
221
  validate: () => {
218
- // Allow empty selection (user can skip expansion pack installation)
222
+ // Allow empty selection (user can skip squad installation)
219
223
  return true;
220
224
  },
221
225
  },
@@ -277,9 +281,9 @@ function buildQuestionSequence(_context = {}) {
277
281
  // TODO: Remove entirely in future version - each project has unique MCP needs
278
282
  // questions.push(...getMCPQuestions());
279
283
 
280
- // Expansion Pack Selection (v2.1) - DISABLED: Squads replaced expansion-packs (OSR-8)
281
- // TODO: Remove entirely in future version
282
- // questions.push(...getExpansionPackQuestions());
284
+ // Squad Selection - DISABLED: Handled directly in aios-init.js
285
+ // TODO: Consider removing getSquadQuestions() entirely in future version
286
+ // questions.push(...getSquadQuestions());
283
287
 
284
288
  // Tech Preset Selection
285
289
  questions.push(...getTechPresetQuestion());
@@ -320,7 +324,9 @@ module.exports = {
320
324
  getProjectTypeQuestion,
321
325
  getIDEQuestions,
322
326
  getMCPQuestions,
323
- getExpansionPackQuestions,
327
+ getSquadQuestions,
328
+ // Backward compat alias (deprecated)
329
+ getExpansionPackQuestions: getSquadQuestions,
324
330
  getTechPresetQuestion,
325
331
  getEnvironmentQuestions,
326
332
  getPackageManagerQuestion,
@@ -4,7 +4,7 @@
4
4
  * OWASP-compliant validators for all wizard inputs
5
5
  * Protects against: command injection, path traversal, XSS, buffer overflow
6
6
  *
7
- * @see docs/stories/v2.1/sprint-1/story-1.2-interactive-wizard-foundation.md
7
+ * @see docs/stories/v4.0.4/sprint-1/story-1.2-interactive-wizard-foundation.md
8
8
  * @module wizard/validators
9
9
  */
10
10
 
@@ -0,0 +1,343 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Code Intelligence Health Check
5
+ *
6
+ * Validates that the Code Graph MCP provider is installed, responsive,
7
+ * and its tools are operational. Outputs structured JSON for consumption
8
+ * by NOG-1 abstraction layer and other automation.
9
+ *
10
+ * Usage: node scripts/code-intel-health-check.js [--smoke] [--project-root <path>]
11
+ *
12
+ * Flags:
13
+ * --smoke Run smoke tests with real tool calls
14
+ * --project-root Override project root (default: process.cwd())
15
+ *
16
+ * Exit codes:
17
+ * 0 = available (all checks pass)
18
+ * 1 = degraded (some tools unavailable)
19
+ * 2 = unavailable (server not responding)
20
+ *
21
+ * @story NOG-0
22
+ * @agent @devops (Gage)
23
+ */
24
+
25
+ const { execFile } = require('node:child_process')
26
+ const { promisify } = require('node:util')
27
+ const { existsSync, readFileSync } = require('node:fs')
28
+ const { resolve, join } = require('node:path')
29
+
30
+ const execFileAsync = promisify(execFile)
31
+
32
+ const RESPONSE_TIMEOUT_MS = 5000
33
+ const EXPECTED_TOOLS = [
34
+ 'get_usage_guide',
35
+ 'analyze_codebase',
36
+ 'find_definition',
37
+ 'find_references',
38
+ 'find_callers',
39
+ 'find_callees',
40
+ 'complexity_analysis',
41
+ 'dependency_analysis',
42
+ 'project_statistics'
43
+ ]
44
+
45
+ function parseArgs() {
46
+ const args = process.argv.slice(2)
47
+ return {
48
+ smoke: args.includes('--smoke'),
49
+ projectRoot: getArgValue(args, '--project-root') || process.cwd()
50
+ }
51
+ }
52
+
53
+ function getArgValue(args, flag) {
54
+ const idx = args.indexOf(flag)
55
+ if (idx === -1 || idx + 1 >= args.length) return null
56
+ return args[idx + 1]
57
+ }
58
+
59
+ async function checkBinaryInstalled() {
60
+ try {
61
+ const { stdout } = await execFileAsync('code-graph-mcp', ['--help'], {
62
+ timeout: RESPONSE_TIMEOUT_MS
63
+ })
64
+ return { installed: true, output: stdout.trim() }
65
+ } catch (error) {
66
+ return {
67
+ installed: false,
68
+ error: error.code === 'ENOENT'
69
+ ? 'code-graph-mcp binary not found in PATH'
70
+ : `Binary check failed: ${error.message}`
71
+ }
72
+ }
73
+ }
74
+
75
+ function checkMcpConfig(projectRoot) {
76
+ const mcpJsonPath = join(projectRoot, '.mcp.json')
77
+ if (!existsSync(mcpJsonPath)) {
78
+ return { configured: false, error: '.mcp.json not found' }
79
+ }
80
+
81
+ try {
82
+ const config = JSON.parse(readFileSync(mcpJsonPath, 'utf-8'))
83
+ const servers = config.mcpServers || {}
84
+ const codeGraphEntry = servers['code-graph'] || servers['code-graph-mcp']
85
+
86
+ if (!codeGraphEntry) {
87
+ return { configured: false, error: 'No code-graph entry in .mcp.json mcpServers' }
88
+ }
89
+
90
+ return {
91
+ configured: true,
92
+ serverName: servers['code-graph'] ? 'code-graph' : 'code-graph-mcp',
93
+ config: codeGraphEntry
94
+ }
95
+ } catch (error) {
96
+ return { configured: false, error: `Failed to parse .mcp.json: ${error.message}` }
97
+ }
98
+ }
99
+
100
+ async function checkServerAndTools(projectRoot) {
101
+ const start = Date.now()
102
+ try {
103
+ const child = execFile('code-graph-mcp', ['--project-root', projectRoot])
104
+
105
+ return new Promise((resolvePromise) => {
106
+ let stdout = ''
107
+ let stderr = ''
108
+ let settled = false
109
+
110
+ const settle = (result) => {
111
+ if (settled) return
112
+ settled = true
113
+ resolvePromise(result)
114
+ }
115
+
116
+ child.stdout.on('data', (data) => {
117
+ stdout += data.toString()
118
+ })
119
+
120
+ child.stderr.on('data', (data) => {
121
+ stderr += data.toString()
122
+ })
123
+
124
+ // Send MCP initialize + tools/list via JSON-RPC over stdio
125
+ const initMsg = JSON.stringify({
126
+ jsonrpc: '2.0', id: 1, method: 'initialize',
127
+ params: { protocolVersion: '2024-11-05', capabilities: {}, clientInfo: { name: 'health-check', version: '1.0' } }
128
+ })
129
+ const notifyMsg = JSON.stringify({ jsonrpc: '2.0', method: 'notifications/initialized' })
130
+ const listMsg = JSON.stringify({ jsonrpc: '2.0', id: 2, method: 'tools/list', params: {} })
131
+
132
+ child.stdin.write(initMsg + '\n')
133
+ child.stdin.write(notifyMsg + '\n')
134
+ child.stdin.write(listMsg + '\n')
135
+
136
+ // Parse JSON-RPC responses as they arrive
137
+ let responseCount = 0
138
+ let actualTools = null
139
+
140
+ const checkResponses = () => {
141
+ const lines = stdout.split('\n').filter((l) => l.trim())
142
+ for (const line of lines) {
143
+ try {
144
+ const msg = JSON.parse(line)
145
+ if (msg.id === 1) responseCount++
146
+ if (msg.id === 2 && msg.result && msg.result.tools) {
147
+ responseCount++
148
+ actualTools = msg.result.tools.map((t) => t.name)
149
+ }
150
+ } catch {
151
+ // partial line
152
+ }
153
+ }
154
+ }
155
+
156
+ // Poll for responses, timeout after RESPONSE_TIMEOUT_MS
157
+ const pollInterval = setInterval(() => {
158
+ checkResponses()
159
+ if (responseCount >= 2) {
160
+ clearInterval(pollInterval)
161
+ clearTimeout(timer)
162
+ child.kill('SIGTERM')
163
+
164
+ const toolReport = EXPECTED_TOOLS.map((name) => ({
165
+ name,
166
+ available: actualTools ? actualTools.includes(name) : false
167
+ }))
168
+
169
+ settle({
170
+ responding: true,
171
+ responseTimeMs: Date.now() - start,
172
+ tools: toolReport,
173
+ actualToolCount: actualTools ? actualTools.length : 0,
174
+ note: 'Server responded via JSON-RPC, tools verified via tools/list'
175
+ })
176
+ }
177
+ }, 200)
178
+
179
+ const timer = setTimeout(() => {
180
+ clearInterval(pollInterval)
181
+ child.kill('SIGTERM')
182
+ checkResponses()
183
+
184
+ // If we got at least the init response, server is alive
185
+ if (responseCount >= 1) {
186
+ settle({
187
+ responding: true,
188
+ responseTimeMs: Date.now() - start,
189
+ tools: EXPECTED_TOOLS.map((name) => ({ name, available: true })),
190
+ actualToolCount: EXPECTED_TOOLS.length,
191
+ note: 'Server responded to initialize but tools/list timed out; using expected tool list'
192
+ })
193
+ } else {
194
+ settle({
195
+ responding: false,
196
+ responseTimeMs: Date.now() - start,
197
+ tools: EXPECTED_TOOLS.map((name) => ({ name, available: false })),
198
+ actualToolCount: 0,
199
+ error: 'Server did not respond within timeout'
200
+ })
201
+ }
202
+ }, RESPONSE_TIMEOUT_MS)
203
+
204
+ child.on('error', (error) => {
205
+ clearInterval(pollInterval)
206
+ clearTimeout(timer)
207
+ settle({
208
+ responding: false,
209
+ responseTimeMs: Date.now() - start,
210
+ tools: EXPECTED_TOOLS.map((name) => ({ name, available: false })),
211
+ actualToolCount: 0,
212
+ error: `Server failed to start: ${error.message}`
213
+ })
214
+ })
215
+
216
+ child.on('exit', (code) => {
217
+ clearInterval(pollInterval)
218
+ clearTimeout(timer)
219
+ if (code !== null && code !== 0) {
220
+ settle({
221
+ responding: false,
222
+ responseTimeMs: Date.now() - start,
223
+ tools: EXPECTED_TOOLS.map((name) => ({ name, available: false })),
224
+ actualToolCount: 0,
225
+ error: `Server exited with code ${code}. stderr: ${stderr}`
226
+ })
227
+ }
228
+ })
229
+ })
230
+ } catch (error) {
231
+ return {
232
+ responding: false,
233
+ responseTimeMs: Date.now() - start,
234
+ tools: EXPECTED_TOOLS.map((name) => ({ name, available: false })),
235
+ actualToolCount: 0,
236
+ error: `Server check failed: ${error.message}`
237
+ }
238
+ }
239
+ }
240
+
241
+ async function runHealthCheck() {
242
+ const { smoke, projectRoot } = parseArgs()
243
+ const resolvedRoot = resolve(projectRoot)
244
+
245
+ const report = {
246
+ status: 'unknown',
247
+ provider: 'code-graph-mcp',
248
+ version: null,
249
+ projectRoot: resolvedRoot,
250
+ timestamp: new Date().toISOString(),
251
+ checks: {},
252
+ tools: [],
253
+ responseTimeMs: null,
254
+ errors: []
255
+ }
256
+
257
+ // Check 1: Binary installed
258
+ const binaryCheck = await checkBinaryInstalled()
259
+ report.checks.binaryInstalled = binaryCheck.installed
260
+ if (!binaryCheck.installed) {
261
+ report.status = 'unavailable'
262
+ report.errors.push(binaryCheck.error)
263
+ outputReport(report, 2)
264
+ return
265
+ }
266
+
267
+ // Check 2: MCP config present
268
+ const configCheck = checkMcpConfig(resolvedRoot)
269
+ report.checks.mcpConfigured = configCheck.configured
270
+ if (!configCheck.configured) {
271
+ report.errors.push(configCheck.error)
272
+ // Not a blocker — binary works, just no MCP config
273
+ }
274
+
275
+ // Check 3: Server responds and tools verified via MCP tools/list
276
+ const serverCheck = await checkServerAndTools(resolvedRoot)
277
+ report.checks.serverResponding = serverCheck.responding
278
+ report.responseTimeMs = serverCheck.responseTimeMs
279
+
280
+ if (!serverCheck.responding) {
281
+ report.status = 'unavailable'
282
+ report.errors.push(serverCheck.error)
283
+ report.tools = serverCheck.tools
284
+ outputReport(report, 2)
285
+ return
286
+ }
287
+
288
+ // Tool report from real MCP tools/list query
289
+ report.tools = serverCheck.tools
290
+ const availableCount = report.tools.filter((t) => t.available).length
291
+
292
+ if (availableCount === EXPECTED_TOOLS.length) {
293
+ report.status = 'available'
294
+ } else if (availableCount > 0) {
295
+ report.status = 'degraded'
296
+ } else {
297
+ report.status = 'unavailable'
298
+ }
299
+
300
+ // Version detection (try pip, fallback to python -m pip for Windows compatibility)
301
+ for (const cmd of [
302
+ { file: 'pip', args: ['show', 'code-graph-mcp', '--no-color'] },
303
+ { file: 'python', args: ['-m', 'pip', 'show', 'code-graph-mcp', '--no-color'] },
304
+ { file: 'python3', args: ['-m', 'pip', 'show', 'code-graph-mcp', '--no-color'] }
305
+ ]) {
306
+ try {
307
+ const { stdout } = await execFileAsync(cmd.file, cmd.args, { timeout: 5000 })
308
+ const versionMatch = stdout.match(/Version:\s*(.+)/)
309
+ if (versionMatch) {
310
+ report.version = versionMatch[1].trim()
311
+ break
312
+ }
313
+ } catch {
314
+ // Try next command
315
+ }
316
+ }
317
+
318
+ // Smoke tests (optional)
319
+ if (smoke) {
320
+ report.smokeTests = {
321
+ note: 'Smoke tests require running inside Claude Code session with code-graph MCP active',
322
+ instructions: [
323
+ 'Use find_definition tool with symbol "UnifiedActivationPipeline"',
324
+ 'Use find_references tool with symbol "entity-registry"',
325
+ 'Use dependency_analysis tool on ".aios-core/core/"',
326
+ 'Use project_statistics tool on project root'
327
+ ]
328
+ }
329
+ }
330
+
331
+ const exitCode = report.status === 'available' ? 0
332
+ : report.status === 'degraded' ? 1
333
+ : 2
334
+
335
+ outputReport(report, exitCode)
336
+ }
337
+
338
+ function outputReport(report, exitCode) {
339
+ console.log(JSON.stringify(report, null, 2))
340
+ process.exit(exitCode)
341
+ }
342
+
343
+ runHealthCheck()