septum 0.1.0 → 0.1.2

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 (65) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +100 -100
  3. package/bin/septum.ts +4 -4
  4. package/package.json +63 -62
  5. package/src/cli/commands/check.ts +161 -161
  6. package/src/cli/commands/hook.ts +188 -188
  7. package/src/cli/commands/ingest.ts +29 -29
  8. package/src/cli/commands/init.ts +211 -211
  9. package/src/cli/commands/locate.ts +61 -61
  10. package/src/cli/commands/query.ts +64 -64
  11. package/src/cli/commands/serve.ts +5 -5
  12. package/src/cli/commands/slice.ts +90 -90
  13. package/src/cli/commands/sync.ts +31 -31
  14. package/src/cli/index.ts +199 -178
  15. package/src/cli/templates/hooks.ts +75 -75
  16. package/src/cli/templates/rules.ts +38 -38
  17. package/src/cli/templates/skill.ts +66 -66
  18. package/src/core/boundary/evaluator.ts +210 -210
  19. package/src/core/config/loader.ts +84 -84
  20. package/src/core/config/schema.ts +39 -39
  21. package/src/core/database/client.ts +44 -132
  22. package/src/core/database/repositories/dependency.repository.ts +279 -279
  23. package/src/core/database/repositories/domain.repository.ts +198 -198
  24. package/src/core/database/repositories/file.repository.ts +99 -99
  25. package/src/core/database/repositories/meta.repository.ts +33 -33
  26. package/src/core/database/repositories/symbol.repository.ts +424 -424
  27. package/src/core/database/repository.ts +242 -242
  28. package/src/core/database/schema.ts +120 -0
  29. package/src/core/discovery/topology-detector.ts +418 -418
  30. package/src/core/ingestion/hasher.ts +5 -5
  31. package/src/core/ingestion/pipeline.ts +438 -438
  32. package/src/core/parser/boundary-tracker.ts +128 -128
  33. package/src/core/parser/extractors/base.ts +60 -60
  34. package/src/core/parser/extractors/frontend.ts +194 -194
  35. package/src/core/parser/extractors/go.ts +176 -176
  36. package/src/core/parser/extractors/laravel-semantic.ts +426 -426
  37. package/src/core/parser/extractors/mcp-cli-semantic.ts +199 -199
  38. package/src/core/parser/extractors/nestjs-semantic.ts +119 -119
  39. package/src/core/parser/extractors/php.ts +149 -149
  40. package/src/core/parser/extractors/python.ts +226 -226
  41. package/src/core/parser/extractors/registry.ts +49 -49
  42. package/src/core/parser/extractors/semantic-extractor.interface.ts +21 -21
  43. package/src/core/parser/extractors/typescript.ts +177 -177
  44. package/src/core/parser/tree-sitter.ts +39 -39
  45. package/src/core/resolver/call-graph-tracer.ts +668 -668
  46. package/src/core/resolver/module-resolver.ts +342 -323
  47. package/src/core/resolver/symbol-locator.ts +543 -405
  48. package/src/core/resolver/vertical-slice-tracer.ts +237 -236
  49. package/src/core/session/session-manager.ts +56 -56
  50. package/src/core/telemetry/telemetry.ts +86 -86
  51. package/src/index.ts +18 -18
  52. package/src/mcp/schemas.ts +68 -68
  53. package/src/mcp/server.ts +612 -602
  54. package/src/mcp/tools/check-boundary.ts +102 -102
  55. package/src/mcp/tools/get-domain-catalog.ts +91 -91
  56. package/src/mcp/tools/get-feature-context.ts +116 -116
  57. package/src/mcp/tools/get-symbol-hotspots.ts +49 -49
  58. package/src/mcp/tools/get-symbol-impact.ts +26 -26
  59. package/src/mcp/tools/get-symbol.ts +63 -63
  60. package/src/mcp/tools/locate-symbol.ts +74 -74
  61. package/src/mcp/tools/register-domain.ts +64 -64
  62. package/src/mcp/tools/trace-vertical-slice.ts +102 -102
  63. package/src/types/index.ts +296 -296
  64. package/src/version.ts +3 -0
  65. package/src/core/database/schema.sql +0 -92
@@ -1,49 +1,49 @@
1
- import type { ValidatedSeptumConfig } from "../../core/config/schema.ts";
2
- import type { DatabaseRepository } from "../../core/database/repository.ts";
3
- import type { SymbolKind } from "../../types/index.ts";
4
-
5
- export interface GetSymbolHotspotsArgs {
6
- min_lines?: number;
7
- kind?: SymbolKind;
8
- domain?: string;
9
- limit?: number;
10
- }
11
-
12
- export function handleGetSymbolHotspots(
13
- repo: DatabaseRepository,
14
- _config: ValidatedSeptumConfig,
15
- args: GetSymbolHotspotsArgs
16
- ) {
17
- const minLines = args.min_lines ?? 30;
18
- const limit = args.limit ?? 30;
19
-
20
- const hotspots = repo.getHotspotSymbols({
21
- minLines,
22
- kind: args.kind,
23
- domain: args.domain,
24
- limit,
25
- });
26
-
27
- const payload = {
28
- total_found: hotspots.length,
29
- threshold_min_lines: minLines,
30
- hotspots: hotspots.map((h) => ({
31
- name: h.name,
32
- kind: h.kind,
33
- line_count: h.line_count,
34
- line_span: `${h.line_start}-${h.line_end}`,
35
- file_path: h.file_path,
36
- domain: h.domain_name,
37
- signature: h.signature,
38
- })),
39
- };
40
-
41
- return {
42
- content: [
43
- {
44
- type: "text" as const,
45
- text: JSON.stringify(payload, null, 2),
46
- },
47
- ],
48
- };
49
- }
1
+ import type { ValidatedSeptumConfig } from "../../core/config/schema.ts";
2
+ import type { DatabaseRepository } from "../../core/database/repository.ts";
3
+ import type { SymbolKind } from "../../types/index.ts";
4
+
5
+ export interface GetSymbolHotspotsArgs {
6
+ min_lines?: number;
7
+ kind?: SymbolKind;
8
+ domain?: string;
9
+ limit?: number;
10
+ }
11
+
12
+ export function handleGetSymbolHotspots(
13
+ repo: DatabaseRepository,
14
+ _config: ValidatedSeptumConfig,
15
+ args: GetSymbolHotspotsArgs
16
+ ) {
17
+ const minLines = args.min_lines ?? 30;
18
+ const limit = args.limit ?? 30;
19
+
20
+ const hotspots = repo.getHotspotSymbols({
21
+ minLines,
22
+ kind: args.kind,
23
+ domain: args.domain,
24
+ limit,
25
+ });
26
+
27
+ const payload = {
28
+ total_found: hotspots.length,
29
+ threshold_min_lines: minLines,
30
+ hotspots: hotspots.map((h) => ({
31
+ name: h.name,
32
+ kind: h.kind,
33
+ line_count: h.line_count,
34
+ line_span: `${h.line_start}-${h.line_end}`,
35
+ file_path: h.file_path,
36
+ domain: h.domain_name,
37
+ signature: h.signature,
38
+ })),
39
+ };
40
+
41
+ return {
42
+ content: [
43
+ {
44
+ type: "text" as const,
45
+ text: JSON.stringify(payload, null, 2),
46
+ },
47
+ ],
48
+ };
49
+ }
@@ -1,26 +1,26 @@
1
- import type { ValidatedSeptumConfig } from "../../core/config/schema.ts";
2
- import type { SeptumRepository } from "../../core/database/repository.ts";
3
- import type { GetSymbolImpactArgs } from "../../types/index.ts";
4
-
5
- export function handleGetSymbolImpact(
6
- repo: SeptumRepository,
7
- _config: ValidatedSeptumConfig,
8
- args: GetSymbolImpactArgs
9
- ) {
10
- if (!args.symbol) {
11
- throw new Error(
12
- "Missing required argument: 'symbol' (e.g. 'OrderService::cancelOrder', 'OrderService', or 'cancelOrder')"
13
- );
14
- }
15
-
16
- const impact = repo.getSymbolImpact(args.symbol);
17
-
18
- return {
19
- content: [
20
- {
21
- type: "text" as const,
22
- text: JSON.stringify(impact, null, 2),
23
- },
24
- ],
25
- };
26
- }
1
+ import type { ValidatedSeptumConfig } from "../../core/config/schema.ts";
2
+ import type { SeptumRepository } from "../../core/database/repository.ts";
3
+ import type { GetSymbolImpactArgs } from "../../types/index.ts";
4
+
5
+ export function handleGetSymbolImpact(
6
+ repo: SeptumRepository,
7
+ _config: ValidatedSeptumConfig,
8
+ args: GetSymbolImpactArgs
9
+ ) {
10
+ if (!args.symbol) {
11
+ throw new Error(
12
+ "Missing required argument: 'symbol' (e.g. 'OrderService::cancelOrder', 'OrderService', or 'cancelOrder')"
13
+ );
14
+ }
15
+
16
+ const impact = repo.getSymbolImpact(args.symbol);
17
+
18
+ return {
19
+ content: [
20
+ {
21
+ type: "text" as const,
22
+ text: JSON.stringify(impact, null, 2),
23
+ },
24
+ ],
25
+ };
26
+ }
@@ -1,63 +1,63 @@
1
- import type { ValidatedSeptumConfig } from "../../core/config/schema.ts";
2
- import type { SeptumRepository } from "../../core/database/repository.ts";
3
- import { SymbolLocator } from "../../core/resolver/symbol-locator.ts";
4
- import type { GetSymbolArgs } from "../../types/index.ts";
5
-
6
- export function handleGetSymbol(
7
- repo: SeptumRepository,
8
- _config: ValidatedSeptumConfig,
9
- args: GetSymbolArgs
10
- ) {
11
- if (!args.symbol) {
12
- throw new Error(
13
- "Missing required argument: 'symbol' (e.g. 'OrderController::cancelOrder', 'OrderService', or 'cancelOrder')"
14
- );
15
- }
16
-
17
- const details = repo.getSymbolDetails(args.symbol, {
18
- domain: args.domain,
19
- includeDependencies: args.include_dependencies,
20
- });
21
-
22
- if (details) {
23
- return {
24
- content: [
25
- {
26
- type: "text" as const,
27
- text: JSON.stringify(details, null, 2),
28
- },
29
- ],
30
- };
31
- }
32
-
33
- // Fallback to fuzzy SymbolLocator to provide actionable hints
34
- const locator = new SymbolLocator(repo);
35
- const fallback = locator.locate(args.symbol, args.domain);
36
-
37
- const payload = {
38
- symbol: args.symbol,
39
- found: false,
40
- message: `Symbol '${args.symbol}' was not found in the deterministic catalog.`,
41
- suggestions: fallback.suggestions.map((s) => ({
42
- name: s.name,
43
- similarity_score: Math.round(s.similarity_score * 100) / 100,
44
- exact_range: {
45
- start_line: s.line_start,
46
- end_line: s.line_end,
47
- total_lines: s.line_end - s.line_start + 1,
48
- },
49
- signature: s.signature,
50
- file_path: s.file_path,
51
- })),
52
- sibling_methods: fallback.sibling_methods,
53
- };
54
-
55
- return {
56
- content: [
57
- {
58
- type: "text" as const,
59
- text: JSON.stringify(payload, null, 2),
60
- },
61
- ],
62
- };
63
- }
1
+ import type { ValidatedSeptumConfig } from "../../core/config/schema.ts";
2
+ import type { SeptumRepository } from "../../core/database/repository.ts";
3
+ import { SymbolLocator } from "../../core/resolver/symbol-locator.ts";
4
+ import type { GetSymbolArgs } from "../../types/index.ts";
5
+
6
+ export function handleGetSymbol(
7
+ repo: SeptumRepository,
8
+ _config: ValidatedSeptumConfig,
9
+ args: GetSymbolArgs
10
+ ) {
11
+ if (!args.symbol) {
12
+ throw new Error(
13
+ "Missing required argument: 'symbol' (e.g. 'OrderController::cancelOrder', 'OrderService', or 'cancelOrder')"
14
+ );
15
+ }
16
+
17
+ const details = repo.getSymbolDetails(args.symbol, {
18
+ domain: args.domain,
19
+ includeDependencies: args.include_dependencies,
20
+ });
21
+
22
+ if (details) {
23
+ return {
24
+ content: [
25
+ {
26
+ type: "text" as const,
27
+ text: JSON.stringify(details, null, 2),
28
+ },
29
+ ],
30
+ };
31
+ }
32
+
33
+ // Fallback to fuzzy SymbolLocator to provide actionable hints
34
+ const locator = new SymbolLocator(repo);
35
+ const fallback = locator.locate(args.symbol, args.domain);
36
+
37
+ const payload = {
38
+ symbol: args.symbol,
39
+ found: false,
40
+ message: `Symbol '${args.symbol}' was not found in the deterministic catalog.`,
41
+ suggestions: fallback.suggestions.map((s) => ({
42
+ name: s.name,
43
+ similarity_score: Math.round(s.similarity_score * 100) / 100,
44
+ exact_range: {
45
+ start_line: s.line_start,
46
+ end_line: s.line_end,
47
+ total_lines: s.line_end - s.line_start + 1,
48
+ },
49
+ signature: s.signature,
50
+ file_path: s.file_path,
51
+ })),
52
+ sibling_methods: fallback.sibling_methods,
53
+ };
54
+
55
+ return {
56
+ content: [
57
+ {
58
+ type: "text" as const,
59
+ text: JSON.stringify(payload, null, 2),
60
+ },
61
+ ],
62
+ };
63
+ }
@@ -1,74 +1,74 @@
1
- import type { ValidatedSeptumConfig } from "../../core/config/schema.ts";
2
- import type { SeptumRepository } from "../../core/database/repository.ts";
3
- import {
4
- SymbolLocator,
5
- type SymbolLocationResult,
6
- } from "../../core/resolver/symbol-locator.ts";
7
-
8
- export interface LocateSymbolArgs {
9
- query: string;
10
- domain?: string;
11
- }
12
-
13
- export function handleLocateSymbol(
14
- repo: SeptumRepository,
15
- config: ValidatedSeptumConfig,
16
- args: LocateSymbolArgs
17
- ) {
18
- if (!args.query) {
19
- throw new Error(
20
- "Missing required argument: 'query' (e.g. 'OrderController::calculateTotal' or error log snippet)"
21
- );
22
- }
23
-
24
- const locator = new SymbolLocator(repo);
25
- const result: SymbolLocationResult = locator.locate(args.query, args.domain);
26
-
27
- let formattedText = "";
28
-
29
- if (result.found) {
30
- formattedText = `=== SYMBOL LOCATED ===\n`;
31
- formattedText += `Status: FOUND (Exact Match)\n`;
32
- formattedText += `File: ${result.file_path}\n`;
33
- if (result.exact_symbol) {
34
- formattedText += `Symbol: ${result.exact_symbol.name} [${result.exact_symbol.kind.toUpperCase()}]\n`;
35
- formattedText += `Signature: ${result.exact_symbol.signature}\n`;
36
- formattedText += `Lines: ${result.exact_symbol.line_start} - ${result.exact_symbol.line_end}\n`;
37
- }
38
- if (result.sibling_methods && result.sibling_methods.length > 0) {
39
- formattedText += `\nSibling Methods in ${result.container_name || "Container"} (${result.sibling_methods.length}):\n`;
40
- formattedText += ` ${result.sibling_methods.join(", ")}\n`;
41
- }
42
- } else {
43
- formattedText = `=== SYMBOL NOT FOUND / DIAGNOSTIC RESOLUTION ===\n`;
44
- formattedText += `Status: NOT FOUND\n`;
45
- if (result.file_path) {
46
- formattedText += `Target File: ${result.file_path}\n`;
47
- formattedText += `Target Container: ${result.container_name || "Unknown"}\n`;
48
- }
49
- formattedText += `Message: ${result.message}\n`;
50
-
51
- if (result.sibling_methods && result.sibling_methods.length > 0) {
52
- formattedText += `\nExisting Methods in ${result.container_name || "Container"} (${result.sibling_methods.length}):\n`;
53
- formattedText += ` ${result.sibling_methods.join(", ")}\n`;
54
- }
55
-
56
- if (result.suggestions.length > 0) {
57
- formattedText += `\nTop Suggestions (Ranked by Similarity):\n`;
58
- for (const sug of result.suggestions) {
59
- const percent = Math.round(sug.similarity_score * 100);
60
- formattedText += ` • ${sug.name} [${percent}% match] -> lines ${sug.line_start}-${sug.line_end} (${sug.signature})\n`;
61
- }
62
- }
63
- }
64
-
65
- return {
66
- content: [
67
- {
68
- type: "text",
69
- text: formattedText.trim(),
70
- },
71
- ],
72
- metadata: result,
73
- };
74
- }
1
+ import type { ValidatedSeptumConfig } from "../../core/config/schema.ts";
2
+ import type { SeptumRepository } from "../../core/database/repository.ts";
3
+ import {
4
+ SymbolLocator,
5
+ type SymbolLocationResult,
6
+ } from "../../core/resolver/symbol-locator.ts";
7
+
8
+ export interface LocateSymbolArgs {
9
+ query: string;
10
+ domain?: string;
11
+ }
12
+
13
+ export function handleLocateSymbol(
14
+ repo: SeptumRepository,
15
+ config: ValidatedSeptumConfig,
16
+ args: LocateSymbolArgs
17
+ ) {
18
+ if (!args.query) {
19
+ throw new Error(
20
+ "Missing required argument: 'query' (e.g. 'OrderController::calculateTotal' or error log snippet)"
21
+ );
22
+ }
23
+
24
+ const locator = new SymbolLocator(repo);
25
+ const result: SymbolLocationResult = locator.locate(args.query, args.domain);
26
+
27
+ let formattedText = "";
28
+
29
+ if (result.found) {
30
+ formattedText = `=== SYMBOL LOCATED ===\n`;
31
+ formattedText += `Status: FOUND (Exact Match)\n`;
32
+ formattedText += `File: ${result.file_path}\n`;
33
+ if (result.exact_symbol) {
34
+ formattedText += `Symbol: ${result.exact_symbol.name} [${result.exact_symbol.kind.toUpperCase()}]\n`;
35
+ formattedText += `Signature: ${result.exact_symbol.signature}\n`;
36
+ formattedText += `Lines: ${result.exact_symbol.line_start} - ${result.exact_symbol.line_end}\n`;
37
+ }
38
+ if (result.sibling_methods && result.sibling_methods.length > 0) {
39
+ formattedText += `\nSibling Methods in ${result.container_name || "Container"} (${result.sibling_methods.length}):\n`;
40
+ formattedText += ` ${result.sibling_methods.join(", ")}\n`;
41
+ }
42
+ } else {
43
+ formattedText = `=== SYMBOL NOT FOUND / DIAGNOSTIC RESOLUTION ===\n`;
44
+ formattedText += `Status: NOT FOUND\n`;
45
+ if (result.file_path) {
46
+ formattedText += `Target File: ${result.file_path}\n`;
47
+ formattedText += `Target Container: ${result.container_name || "Unknown"}\n`;
48
+ }
49
+ formattedText += `Message: ${result.message}\n`;
50
+
51
+ if (result.sibling_methods && result.sibling_methods.length > 0) {
52
+ formattedText += `\nExisting Methods in ${result.container_name || "Container"} (${result.sibling_methods.length}):\n`;
53
+ formattedText += ` ${result.sibling_methods.join(", ")}\n`;
54
+ }
55
+
56
+ if (result.suggestions.length > 0) {
57
+ formattedText += `\nTop Suggestions (Ranked by Similarity):\n`;
58
+ for (const sug of result.suggestions) {
59
+ const percent = Math.round(sug.similarity_score * 100);
60
+ formattedText += ` • ${sug.name} [${percent}% match] -> lines ${sug.line_start}-${sug.line_end} (${sug.signature})\n`;
61
+ }
62
+ }
63
+ }
64
+
65
+ return {
66
+ content: [
67
+ {
68
+ type: "text",
69
+ text: formattedText.trim(),
70
+ },
71
+ ],
72
+ metadata: result,
73
+ };
74
+ }
@@ -1,64 +1,64 @@
1
- import type { ValidatedSeptumConfig } from "../../core/config/schema.ts";
2
- import type { SeptumRepository } from "../../core/database/repository.ts";
3
- import { IngestionPipeline, type IngestionMetrics } from "../../core/ingestion/pipeline.ts";
4
- import type { DomainConfig } from "../../types/index.ts";
5
-
6
- export interface RegisterDomainArgs {
7
- name: string;
8
- root: string;
9
- description?: string;
10
- allowed_dependencies?: string[];
11
- forbidden_dependencies?: string[];
12
- archetypes?: Record<string, string>;
13
- ingest_now?: boolean;
14
- }
15
-
16
- export async function handleRegisterDomain(
17
- repo: SeptumRepository,
18
- config: ValidatedSeptumConfig,
19
- args: RegisterDomainArgs
20
- ) {
21
- if (!args.name || !args.root) {
22
- throw new Error("Missing required arguments: 'name' and 'root' are required.");
23
- }
24
-
25
- const domainConfig: DomainConfig = {
26
- root: args.root,
27
- description: args.description || `Domain: ${args.name}`,
28
- allowed_dependencies: args.allowed_dependencies || [],
29
- forbidden_dependencies: args.forbidden_dependencies || [],
30
- archetypes: args.archetypes || {},
31
- };
32
-
33
- const domainId = repo.domains.upsertDomain(args.name, domainConfig);
34
-
35
- config.domains[args.name] = domainConfig;
36
-
37
- let ingestionMetrics: IngestionMetrics | null = null;
38
- if (args.ingest_now) {
39
- const pipeline = new IngestionPipeline(repo);
40
- ingestionMetrics = await pipeline.run(config);
41
- }
42
-
43
- return {
44
- content: [
45
- {
46
- type: "text" as const,
47
- text: JSON.stringify(
48
- {
49
- status: "success",
50
- message: `Domain '${args.name}' successfully registered in SQLite SSOT.${
51
- args.ingest_now ? " Initial ingestion completed." : ""
52
- }`,
53
- domain_id: domainId,
54
- domain: args.name,
55
- config: domainConfig,
56
- ...(ingestionMetrics ? { ingestion_metrics: ingestionMetrics } : {}),
57
- },
58
- null,
59
- 2
60
- ),
61
- },
62
- ],
63
- };
64
- }
1
+ import type { ValidatedSeptumConfig } from "../../core/config/schema.ts";
2
+ import type { SeptumRepository } from "../../core/database/repository.ts";
3
+ import { IngestionPipeline, type IngestionMetrics } from "../../core/ingestion/pipeline.ts";
4
+ import type { DomainConfig } from "../../types/index.ts";
5
+
6
+ export interface RegisterDomainArgs {
7
+ name: string;
8
+ root: string;
9
+ description?: string;
10
+ allowed_dependencies?: string[];
11
+ forbidden_dependencies?: string[];
12
+ archetypes?: Record<string, string>;
13
+ ingest_now?: boolean;
14
+ }
15
+
16
+ export async function handleRegisterDomain(
17
+ repo: SeptumRepository,
18
+ config: ValidatedSeptumConfig,
19
+ args: RegisterDomainArgs
20
+ ) {
21
+ if (!args.name || !args.root) {
22
+ throw new Error("Missing required arguments: 'name' and 'root' are required.");
23
+ }
24
+
25
+ const domainConfig: DomainConfig = {
26
+ root: args.root,
27
+ description: args.description || `Domain: ${args.name}`,
28
+ allowed_dependencies: args.allowed_dependencies || [],
29
+ forbidden_dependencies: args.forbidden_dependencies || [],
30
+ archetypes: args.archetypes || {},
31
+ };
32
+
33
+ const domainId = repo.domains.upsertDomain(args.name, domainConfig);
34
+
35
+ config.domains[args.name] = domainConfig;
36
+
37
+ let ingestionMetrics: IngestionMetrics | null = null;
38
+ if (args.ingest_now) {
39
+ const pipeline = new IngestionPipeline(repo);
40
+ ingestionMetrics = await pipeline.run(config);
41
+ }
42
+
43
+ return {
44
+ content: [
45
+ {
46
+ type: "text" as const,
47
+ text: JSON.stringify(
48
+ {
49
+ status: "success",
50
+ message: `Domain '${args.name}' successfully registered in SQLite SSOT.${
51
+ args.ingest_now ? " Initial ingestion completed." : ""
52
+ }`,
53
+ domain_id: domainId,
54
+ domain: args.name,
55
+ config: domainConfig,
56
+ ...(ingestionMetrics ? { ingestion_metrics: ingestionMetrics } : {}),
57
+ },
58
+ null,
59
+ 2
60
+ ),
61
+ },
62
+ ],
63
+ };
64
+ }