cntx-ui 2.0.13 → 3.0.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 (49) hide show
  1. package/README.md +51 -339
  2. package/VISION.md +110 -0
  3. package/bin/cntx-ui-mcp.sh +3 -0
  4. package/bin/cntx-ui.js +138 -55
  5. package/lib/agent-runtime.js +301 -0
  6. package/lib/agent-tools.js +370 -0
  7. package/lib/api-router.js +1161 -0
  8. package/lib/bundle-manager.js +236 -0
  9. package/lib/configuration-manager.js +760 -0
  10. package/lib/database-manager.js +397 -0
  11. package/lib/file-system-manager.js +489 -0
  12. package/lib/heuristics-manager.js +527 -0
  13. package/lib/mcp-server.js +1125 -2
  14. package/lib/semantic-splitter.js +225 -491
  15. package/lib/simple-vector-store.js +98 -0
  16. package/lib/websocket-manager.js +470 -0
  17. package/package.json +19 -25
  18. package/server.js +742 -1935
  19. package/templates/TOOLS.md +41 -0
  20. package/templates/activities/README.md +67 -0
  21. package/templates/activities/activities/create-project-bundles/README.md +84 -0
  22. package/templates/activities/activities/create-project-bundles/notes.md +98 -0
  23. package/templates/activities/activities/create-project-bundles/progress.md +63 -0
  24. package/templates/activities/activities/create-project-bundles/tasks.md +39 -0
  25. package/templates/activities/activities.json +219 -0
  26. package/templates/activities/lib/.markdownlint.jsonc +18 -0
  27. package/templates/activities/lib/create-activity.mdc +63 -0
  28. package/templates/activities/lib/generate-tasks.mdc +64 -0
  29. package/templates/activities/lib/process-task-list.mdc +52 -0
  30. package/templates/agent-config.yaml +65 -0
  31. package/templates/agent-instructions.md +234 -0
  32. package/templates/agent-rules/capabilities/activities-system.md +147 -0
  33. package/templates/agent-rules/capabilities/bundle-system.md +131 -0
  34. package/templates/agent-rules/capabilities/vector-search.md +135 -0
  35. package/templates/agent-rules/core/codebase-navigation.md +91 -0
  36. package/templates/agent-rules/core/performance-hierarchy.md +48 -0
  37. package/templates/agent-rules/core/response-formatting.md +120 -0
  38. package/templates/agent-rules/project-specific/architecture.md +145 -0
  39. package/templates/config.json +76 -0
  40. package/templates/hidden-files.json +14 -0
  41. package/web/dist/assets/index-B2OdTzzI.css +1 -0
  42. package/web/dist/assets/index-D0tBsKiR.js +2016 -0
  43. package/web/dist/cntx-ui.svg +18 -0
  44. package/web/dist/index.html +25 -8
  45. package/lib/semantic-integration.js +0 -441
  46. package/mcp-config-example.json +0 -9
  47. package/web/dist/assets/index-Ci1Q-YrQ.js +0 -611
  48. package/web/dist/assets/index-IUp4q_fr.css +0 -1
  49. package/web/dist/vite.svg +0 -21
@@ -0,0 +1,18 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!-- Generated by Pixelmator Pro 3.4.3 -->
3
+ <svg width="31" height="32" viewBox="0 0 31 32" xmlns="http://www.w3.org/2000/svg">
4
+ <g id="Group-copy">
5
+ <path id="Path" fill="none" stroke="#828080" stroke-linecap="round" stroke-linejoin="round" d="M 2.431387 18.088001 C 1.545252 18.620388 1.002266 19.57761 1 20.611374 L 1 25.392504 C 1.002266 26.426268 1.545252 27.383492 2.431387 27.915876 L 6.858358 30.572058 C 7.793726 31.134027 8.962845 31.134027 9.898211 30.572058 L 15.756571 27.059996 L 15.756571 18.943882 L 8.378285 14.516911 L 2.431387 18.088001 Z"/>
6
+ <path id="path1" fill="none" stroke="#828080" stroke-linecap="round" stroke-linejoin="round" d="M 8.378285 23.370853 L 1.383671 19.16523"/>
7
+ <path id="path2" fill="none" stroke="#828080" stroke-linecap="round" stroke-linejoin="round" d="M 8.378285 23.370853 L 15.756571 18.943882"/>
8
+ <path id="path3" fill="none" stroke="#828080" stroke-linecap="round" stroke-linejoin="round" d="M 8.378285 23.370853 L 8.378285 31"/>
9
+ <path id="path4" fill="none" stroke="#828080" stroke-linecap="round" stroke-linejoin="round" d="M 15.756571 18.943882 L 15.756571 27.059996 L 21.614929 30.572058 C 22.550297 31.134027 23.719416 31.134027 24.654783 30.572058 L 29.081755 27.915876 C 29.96789 27.383492 30.510876 26.426268 30.513142 25.392504 L 30.513142 20.611374 C 30.510876 19.57761 29.96789 18.620388 29.081755 18.088001 L 23.134855 14.516911 L 15.756571 18.943882 Z"/>
10
+ <path id="path5" fill="none" stroke="#828080" stroke-linecap="round" stroke-linejoin="round" d="M 23.134855 23.370853 L 15.756571 18.943882"/>
11
+ <path id="path6" fill="none" stroke="#828080" stroke-linecap="round" stroke-linejoin="round" d="M 23.134855 23.370853 L 30.129469 19.16523"/>
12
+ <path id="path7" fill="none" stroke="#828080" stroke-linecap="round" stroke-linejoin="round" d="M 23.134855 23.370853 L 23.134855 31"/>
13
+ <path id="path8" fill="none" stroke="#828080" stroke-linecap="round" stroke-linejoin="round" d="M 9.809672 5.544916 C 8.923537 6.077305 8.380551 7.034525 8.378285 8.068291 L 8.378285 14.516911 L 15.756571 18.943882 L 23.134855 14.516911 L 23.134855 8.068291 C 23.132589 7.034525 22.589605 6.077305 21.70347 5.544916 L 17.276497 2.888731 C 16.341131 2.326765 15.17201 2.326765 14.236644 2.888731 L 9.809672 5.544916 Z"/>
14
+ <path id="path9" fill="none" stroke="#828080" stroke-linecap="round" stroke-linejoin="round" d="M 15.756571 10.827768 L 8.761956 6.622145"/>
15
+ <path id="path10" fill="none" stroke="#828080" stroke-linecap="round" stroke-linejoin="round" d="M 15.756571 10.827768 L 22.751184 6.622145"/>
16
+ <path id="path11" fill="none" stroke="#828080" stroke-linecap="round" stroke-linejoin="round" d="M 15.756571 18.943882 L 15.756571 10.827768"/>
17
+ </g>
18
+ </svg>
@@ -1,15 +1,32 @@
1
- <!doctype html>
1
+ <!DOCTYPE html>
2
2
  <html lang="en">
3
3
  <head>
4
4
  <meta charset="UTF-8" />
5
- <link rel="icon" type="image/svg+xml" href="./vite.svg" />
6
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
5
+ <link
6
+ rel="icon"
7
+ type="image/svg+xml"
8
+ href="./cntx-ui.svg"
9
+ />
10
+ <meta
11
+ name="viewport"
12
+ content="width=device-width, initial-scale=1.0"
13
+ />
7
14
  <title>cntx-ui</title>
8
- <link rel="preconnect" href="https://fonts.googleapis.com">
9
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
10
- <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:ital,wght@0,100..800;1,100..800&display=swap" rel="stylesheet">
11
- <script type="module" crossorigin src="./assets/index-Ci1Q-YrQ.js"></script>
12
- <link rel="stylesheet" crossorigin href="./assets/index-IUp4q_fr.css">
15
+ <link
16
+ rel="preconnect"
17
+ href="https://fonts.googleapis.com"
18
+ />
19
+ <link
20
+ rel="preconnect"
21
+ href="https://fonts.gstatic.com"
22
+ crossorigin
23
+ />
24
+ <link
25
+ href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:ital,wght@0,100..800;1,100..800&display=swap"
26
+ rel="stylesheet"
27
+ />
28
+ <script type="module" crossorigin src="./assets/index-D0tBsKiR.js"></script>
29
+ <link rel="stylesheet" crossorigin href="./assets/index-B2OdTzzI.css">
13
30
  </head>
14
31
  <body>
15
32
  <div id="root"></div>
@@ -1,441 +0,0 @@
1
- /**
2
- * Semantic Chunking Integration for cntx-ui Server
3
- * Extends the existing server with semantic analysis capabilities
4
- */
5
-
6
- import SemanticChunker from './semantic-chunker.js'
7
- import { readFileSync, writeFileSync } from 'fs'
8
- import { join } from 'path'
9
-
10
- class SemanticIntegration {
11
- constructor(projectPath = process.cwd()) {
12
- this.projectPath = projectPath
13
- this.chunker = new SemanticChunker({
14
- includeImports: true,
15
- includeExports: true,
16
- detectComponentTypes: true,
17
- groupRelatedFiles: true,
18
- minChunkSize: 100,
19
- maxChunkSize: 10000
20
- })
21
- }
22
-
23
- /**
24
- * Analyze project and generate semantic bundle suggestions
25
- */
26
- async generateSemanticBundles() {
27
- try {
28
- console.log('šŸ” Analyzing project for semantic chunking...')
29
-
30
- // Analyze different file types separately
31
- const analyses = await Promise.all([
32
- this.chunker.analyzeProject(this.projectPath, ['web/src/components/**/*.{tsx,ts}']),
33
- this.chunker.analyzeProject(this.projectPath, ['web/src/hooks/**/*.{tsx,ts}']),
34
- this.chunker.analyzeProject(this.projectPath, ['web/src/lib/**/*.{tsx,ts}']),
35
- this.chunker.analyzeProject(this.projectPath, ['web/src/utils/**/*.{tsx,ts}']),
36
- this.chunker.analyzeProject(this.projectPath, ['lib/**/*.js']),
37
- ])
38
-
39
- const [componentsAnalysis, hooksAnalysis, libAnalysis, utilsAnalysis, serverAnalysis] = analyses
40
-
41
- // Generate bundle suggestions
42
- const suggestions = this.generateBundleSuggestions([
43
- { name: 'components', analysis: componentsAnalysis },
44
- { name: 'hooks', analysis: hooksAnalysis },
45
- { name: 'lib', analysis: libAnalysis },
46
- { name: 'utils', analysis: utilsAnalysis },
47
- { name: 'server', analysis: serverAnalysis }
48
- ])
49
-
50
- console.log('āœ… Semantic analysis complete!')
51
-
52
- return {
53
- timestamp: new Date().toISOString(),
54
- projectPath: this.projectPath,
55
- suggestions,
56
- analyses: {
57
- components: this.chunker.formatResults(componentsAnalysis),
58
- hooks: this.chunker.formatResults(hooksAnalysis),
59
- lib: this.chunker.formatResults(libAnalysis),
60
- utils: this.chunker.formatResults(utilsAnalysis),
61
- server: this.chunker.formatResults(serverAnalysis)
62
- },
63
- summary: this.generateOverallSummary(analyses)
64
- }
65
- } catch (error) {
66
- console.error('āŒ Semantic analysis failed:', error.message)
67
- throw error
68
- }
69
- }
70
-
71
- /**
72
- * Generate bundle configuration suggestions based on semantic analysis
73
- */
74
- generateBundleSuggestions(namedAnalyses) {
75
- const suggestions = {
76
- recommended: {},
77
- alternative: {},
78
- notes: []
79
- }
80
-
81
- for (const { name, analysis } of namedAnalyses) {
82
- if (analysis.chunks.length === 0) continue
83
-
84
- // Recommended: Group by semantic purpose
85
- const purposeGroups = this.groupChunksByPurpose(analysis.chunks)
86
-
87
- for (const [purpose, chunks] of Object.entries(purposeGroups)) {
88
- const bundleName = `${name}-${purpose}`
89
- const patterns = this.generatePatternsForChunks(chunks)
90
-
91
- suggestions.recommended[bundleName] = patterns
92
- }
93
-
94
- // Alternative: Group by directory structure
95
- const dirGroups = this.groupChunksByDirectory(analysis.chunks)
96
-
97
- for (const [dir, chunks] of Object.entries(dirGroups)) {
98
- const bundleName = `${name}-${dir}`
99
- const patterns = this.generatePatternsForChunks(chunks)
100
-
101
- suggestions.alternative[bundleName] = patterns
102
- }
103
- }
104
-
105
- // Add specialized bundles
106
- this.addSpecializedBundles(suggestions, namedAnalyses)
107
-
108
- // Add optimization notes
109
- this.addOptimizationNotes(suggestions, namedAnalyses)
110
-
111
- return suggestions
112
- }
113
-
114
- /**
115
- * Group chunks by their primary purpose
116
- */
117
- groupChunksByPurpose(chunks) {
118
- const groups = {}
119
-
120
- for (const chunk of chunks) {
121
- const purpose = chunk.purpose || 'misc'
122
- if (!groups[purpose]) {
123
- groups[purpose] = []
124
- }
125
- groups[purpose].push(chunk)
126
- }
127
-
128
- return groups
129
- }
130
-
131
- /**
132
- * Group chunks by directory structure
133
- */
134
- groupChunksByDirectory(chunks) {
135
- const groups = {}
136
-
137
- for (const chunk of chunks) {
138
- // Extract directory from chunk name or files
139
- const dir = chunk.name.split('-')[0] || 'misc'
140
- if (!groups[dir]) {
141
- groups[dir] = []
142
- }
143
- groups[dir].push(chunk)
144
- }
145
-
146
- return groups
147
- }
148
-
149
- /**
150
- * Generate glob patterns for chunks
151
- */
152
- generatePatternsForChunks(chunks) {
153
- const patterns = new Set()
154
-
155
- for (const chunk of chunks) {
156
- for (const file of chunk.files) {
157
- // Convert file paths to glob patterns
158
- const pattern = this.fileToGlobPattern(file)
159
- patterns.add(pattern)
160
- }
161
- }
162
-
163
- return Array.from(patterns)
164
- }
165
-
166
- /**
167
- * Convert file path to glob pattern
168
- */
169
- fileToGlobPattern(filePath) {
170
- // Extract directory and create pattern
171
- const parts = filePath.split('/')
172
- const dir = parts.slice(0, -1).join('/')
173
- const ext = parts[parts.length - 1].split('.').pop()
174
-
175
- return `${dir}/**/*.${ext}`
176
- }
177
-
178
- /**
179
- * Add specialized bundle suggestions
180
- */
181
- addSpecializedBundles(suggestions, namedAnalyses) {
182
- // UI Components bundle
183
- const componentsAnalysis = namedAnalyses.find(a => a.name === 'components')?.analysis
184
- if (componentsAnalysis?.chunks.length > 0) {
185
- const uiComponents = componentsAnalysis.chunks.filter(c =>
186
- c.name.includes('ui') || c.tags.includes('has-components')
187
- )
188
-
189
- if (uiComponents.length > 0) {
190
- suggestions.recommended['ui-system'] = [
191
- 'web/src/components/ui/**/*.tsx',
192
- 'web/src/components/theme-*.tsx'
193
- ]
194
- }
195
- }
196
-
197
- // Test files bundle
198
- suggestions.recommended['tests'] = [
199
- '**/*.test.{js,jsx,ts,tsx}',
200
- '**/*.spec.{js,jsx,ts,tsx}',
201
- '**/__tests__/**/*'
202
- ]
203
-
204
- // Configuration bundle
205
- suggestions.recommended['config'] = [
206
- '*.config.{js,ts}',
207
- '*.json',
208
- 'package.json',
209
- 'tsconfig*.json',
210
- '.env*'
211
- ]
212
-
213
- // Documentation bundle
214
- suggestions.recommended['docs'] = [
215
- '**/*.md',
216
- 'docs/**/*',
217
- 'README*'
218
- ]
219
- }
220
-
221
- /**
222
- * Add optimization notes and recommendations
223
- */
224
- addOptimizationNotes(suggestions, namedAnalyses) {
225
- suggestions.notes = []
226
-
227
- // Analyze bundle sizes
228
- const totalFiles = namedAnalyses.reduce((sum, a) => sum + a.analysis.summary.totalFiles, 0)
229
- const totalChunks = namedAnalyses.reduce((sum, a) => sum + a.analysis.summary.totalChunks, 0)
230
-
231
- suggestions.notes.push({
232
- type: 'info',
233
- message: `Project has ${totalFiles} files organized into ${totalChunks} semantic chunks`
234
- })
235
-
236
- // Check for large files
237
- const largeFiles = []
238
- for (const { analysis } of namedAnalyses) {
239
- for (const [path, file] of Object.entries(analysis.files)) {
240
- if (file.lines > 300) {
241
- largeFiles.push({ path, lines: file.lines })
242
- }
243
- }
244
- }
245
-
246
- if (largeFiles.length > 0) {
247
- suggestions.notes.push({
248
- type: 'warning',
249
- message: `${largeFiles.length} files exceed 300 lines and may benefit from splitting`,
250
- details: largeFiles.slice(0, 3).map(f => `${f.path} (${f.lines} lines)`)
251
- })
252
- }
253
-
254
- // Check for high complexity
255
- const complexFiles = []
256
- for (const { analysis } of namedAnalyses) {
257
- for (const [path, file] of Object.entries(analysis.files)) {
258
- if (file.complexity?.level === 'high') {
259
- complexFiles.push({ path, score: file.complexity.score })
260
- }
261
- }
262
- }
263
-
264
- if (complexFiles.length > 0) {
265
- suggestions.notes.push({
266
- type: 'optimization',
267
- message: `${complexFiles.length} files have high complexity and may need refactoring`,
268
- details: complexFiles.slice(0, 3).map(f => `${f.path} (score: ${f.score})`)
269
- })
270
- }
271
-
272
- // Suggest bundle patterns
273
- suggestions.notes.push({
274
- type: 'suggestion',
275
- message: 'Consider using semantic bundles for better AI context understanding',
276
- details: [
277
- 'UI components grouped by functionality',
278
- 'Hooks grouped by domain logic',
279
- 'Utilities grouped by purpose',
280
- 'Tests separated for focused debugging'
281
- ]
282
- })
283
- }
284
-
285
- /**
286
- * Generate overall project summary
287
- */
288
- generateOverallSummary(analyses) {
289
- const totalFiles = analyses.reduce((sum, a) => sum + a.summary.totalFiles, 0)
290
- const totalSize = analyses.reduce((sum, a) => sum + a.summary.totalSize, 0)
291
- const totalLines = analyses.reduce((sum, a) => sum + a.summary.totalLines, 0)
292
- const totalChunks = analyses.reduce((sum, a) => sum + a.summary.totalChunks, 0)
293
-
294
- const fileTypes = {}
295
- const filePurposes = {}
296
- const complexityDistribution = {}
297
-
298
- for (const analysis of analyses) {
299
- // Merge file types
300
- for (const [type, count] of Object.entries(analysis.summary.fileTypes)) {
301
- fileTypes[type] = (fileTypes[type] || 0) + count
302
- }
303
-
304
- // Merge purposes
305
- for (const [purpose, count] of Object.entries(analysis.summary.filePurposes)) {
306
- filePurposes[purpose] = (filePurposes[purpose] || 0) + count
307
- }
308
-
309
- // Merge complexity
310
- for (const [level, count] of Object.entries(analysis.summary.complexityDistribution)) {
311
- complexityDistribution[level] = (complexityDistribution[level] || 0) + count
312
- }
313
- }
314
-
315
- return {
316
- totalFiles,
317
- totalSize,
318
- totalLines,
319
- totalChunks,
320
- fileTypes,
321
- filePurposes,
322
- complexityDistribution,
323
- averageFileSize: Math.round(totalSize / totalFiles),
324
- averageLinesPerFile: Math.round(totalLines / totalFiles),
325
- formattedSize: this.formatBytes(totalSize)
326
- }
327
- }
328
-
329
- /**
330
- * Convert semantic suggestions to cntx-ui bundle format
331
- */
332
- convertToConfigFormat(suggestions) {
333
- return {
334
- bundles: {
335
- // Keep existing master bundle
336
- master: ['**/*'],
337
-
338
- // Add recommended semantic bundles
339
- ...suggestions.recommended,
340
-
341
- // Add alternative bundles as commented examples
342
- // ...suggestions.alternative (uncomment to use directory-based grouping)
343
- }
344
- }
345
- }
346
-
347
- /**
348
- * Save analysis results to file
349
- */
350
- async saveAnalysisResults(results, outputPath = 'semantic-analysis.json') {
351
- try {
352
- const fullPath = join(this.projectPath, outputPath)
353
- writeFileSync(fullPath, JSON.stringify(results, null, 2))
354
- console.log(`šŸ“ Analysis saved to ${fullPath}`)
355
- return fullPath
356
- } catch (error) {
357
- console.error('āŒ Failed to save analysis:', error.message)
358
- throw error
359
- }
360
- }
361
-
362
- /**
363
- * Generate bundle config suggestions file
364
- */
365
- async generateBundleConfigSuggestions(results, outputPath = 'semantic-bundle-suggestions.json') {
366
- try {
367
- const config = this.convertToConfigFormat(results.suggestions)
368
- const fullPath = join(this.projectPath, outputPath)
369
-
370
- const output = {
371
- timestamp: results.timestamp,
372
- description: 'Semantic chunking suggestions for cntx-ui bundles',
373
- usage: 'Copy the bundles object to your .cntx/bundles.json file',
374
- ...config,
375
- notes: results.suggestions.notes,
376
- summary: results.summary
377
- }
378
-
379
- writeFileSync(fullPath, JSON.stringify(output, null, 2))
380
- console.log(`šŸ“¦ Bundle suggestions saved to ${fullPath}`)
381
- return fullPath
382
- } catch (error) {
383
- console.error('āŒ Failed to save bundle suggestions:', error.message)
384
- throw error
385
- }
386
- }
387
-
388
- /**
389
- * Format bytes to human readable
390
- */
391
- formatBytes(bytes) {
392
- if (bytes === 0) return '0 Bytes'
393
- const k = 1024
394
- const sizes = ['Bytes', 'KB', 'MB', 'GB']
395
- const i = Math.floor(Math.log(bytes) / Math.log(k))
396
- return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
397
- }
398
- }
399
-
400
- export default SemanticIntegration
401
-
402
- // CLI usage
403
- if (import.meta.url === `file://${process.argv[1]}`) {
404
- const integration = new SemanticIntegration()
405
-
406
- console.log('šŸš€ Starting semantic analysis integration...')
407
-
408
- try {
409
- const results = await integration.generateSemanticBundles()
410
-
411
- // Save full analysis
412
- await integration.saveAnalysisResults(results)
413
-
414
- // Save bundle suggestions
415
- await integration.generateBundleConfigSuggestions(results)
416
-
417
- console.log('\nšŸ“Š Project Summary:')
418
- console.log(`šŸ“ ${results.summary.totalFiles} files analyzed`)
419
- console.log(`šŸ’¾ ${results.summary.formattedSize} total size`)
420
- console.log(`šŸ“¦ ${results.summary.totalChunks} semantic chunks created`)
421
- console.log(`šŸŽÆ ${Object.keys(results.suggestions.recommended).length} recommended bundles`)
422
-
423
- console.log('\nšŸŽÆ Recommended Bundles:')
424
- for (const [name, patterns] of Object.entries(results.suggestions.recommended)) {
425
- console.log(` • ${name}: ${patterns.length} patterns`)
426
- }
427
-
428
- console.log('\nšŸ’” Notes:')
429
- results.suggestions.notes.forEach(note => {
430
- const emoji = note.type === 'warning' ? 'āš ļø' : note.type === 'optimization' ? 'šŸ”§' : 'šŸ’”'
431
- console.log(` ${emoji} ${note.message}`)
432
- })
433
-
434
- console.log('\nāœ… Semantic integration complete!')
435
- console.log('šŸ“„ Check semantic-bundle-suggestions.json for ready-to-use bundle configurations')
436
-
437
- } catch (error) {
438
- console.error('āŒ Integration failed:', error.message)
439
- process.exit(1)
440
- }
441
- }
@@ -1,9 +0,0 @@
1
- {
2
- "mcpServers": {
3
- "cntx-ui": {
4
- "command": "cntx-ui",
5
- "args": ["mcp"],
6
- "cwd": "{{projectDir}}"
7
- }
8
- }
9
- }