septum 0.1.5 → 0.1.6
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.
- package/package.json +1 -1
- package/src/cli/commands/hook.ts +36 -8
- package/src/cli/commands/init.ts +6 -4
- package/src/cli/commands/query.ts +45 -5
- package/src/cli/commands/slice.ts +4 -2
- package/src/cli/index.ts +15 -5
- package/src/cli/templates/hooks.ts +6 -6
- package/src/cli/templates/rules.ts +35 -23
- package/src/cli/templates/skill.ts +47 -51
- package/src/core/boundary/evaluator.ts +198 -1
- package/src/core/config/loader.ts +20 -0
- package/src/core/database/repositories/dependency.repository.ts +249 -63
- package/src/core/database/repositories/domain.repository.ts +96 -39
- package/src/core/database/repositories/file.repository.ts +20 -19
- package/src/core/database/repositories/symbol.repository.ts +44 -5
- package/src/core/database/repository.ts +19 -2
- package/src/core/database/schema.ts +3 -2
- package/src/core/ingestion/pipeline.ts +47 -25
- package/src/core/parser/boundary-tracker.ts +145 -0
- package/src/core/parser/extractors/frontend.ts +43 -90
- package/src/core/parser/extractors/go.ts +200 -21
- package/src/core/parser/extractors/laravel-semantic.ts +18 -12
- package/src/core/parser/extractors/mcp-cli-semantic.ts +173 -44
- package/src/core/parser/extractors/nestjs-semantic.ts +20 -5
- package/src/core/parser/extractors/php.ts +293 -31
- package/src/core/parser/extractors/python.ts +242 -10
- package/src/core/parser/extractors/typescript.ts +604 -117
- package/src/core/parser/wasm-loader.ts +271 -0
- package/src/core/resolver/call-graph-tracer.ts +49 -17
- package/src/core/resolver/module-resolver.ts +87 -16
- package/src/core/resolver/vertical-slice-tracer.ts +33 -11
- package/src/core/topology/environment-detector.ts +109 -6
- package/src/mcp/schemas.ts +146 -29
- package/src/mcp/server.ts +93 -41
- package/src/mcp/tools/check-boundary.ts +176 -40
- package/src/mcp/tools/get-domain-catalog.ts +24 -14
- package/src/mcp/tools/get-environment-topology.ts +3 -6
- package/src/mcp/tools/get-feature-context.ts +35 -20
- package/src/mcp/tools/get-symbol-hotspots.ts +4 -10
- package/src/mcp/tools/locate-symbol.ts +12 -14
- package/src/mcp/tools/register-domain.ts +3 -12
- package/src/mcp/tools/register-feature.ts +22 -31
- package/src/mcp/tools/trace-vertical-slice.ts +8 -8
- package/src/types/index.ts +36 -5
|
@@ -30,6 +30,8 @@ export class McpCliSemanticExtractor implements SemanticSliceExtractor {
|
|
|
30
30
|
|
|
31
31
|
public extractSlices(projectRoot: string, domainId?: number): VerticalSliceCandidate[] {
|
|
32
32
|
const slices: VerticalSliceCandidate[] = [];
|
|
33
|
+
const { toolPrefix, cliBinName } = this.resolveProjectNaming(projectRoot);
|
|
34
|
+
const registeredTools = this.scanRegisteredMcpToolNames(projectRoot);
|
|
33
35
|
|
|
34
36
|
// 1. Extract MCP Tools
|
|
35
37
|
const mcpToolsDir = path.join(projectRoot, "src/mcp/tools");
|
|
@@ -41,30 +43,46 @@ export class McpCliSemanticExtractor implements SemanticSliceExtractor {
|
|
|
41
43
|
|
|
42
44
|
for (const file of toolFiles) {
|
|
43
45
|
const fullPath = path.join(mcpToolsDir, file);
|
|
44
|
-
const relPath = path.relative(projectRoot, fullPath);
|
|
46
|
+
const relPath = path.relative(projectRoot, fullPath).replace(/\\/g, "/");
|
|
45
47
|
const content = fs.readFileSync(fullPath, "utf-8");
|
|
46
|
-
const baseName = path.basename(file, path.extname(file));
|
|
47
|
-
const
|
|
48
|
+
const baseName = path.basename(file, path.extname(file));
|
|
49
|
+
const snakeBase = baseName.replace(/-/g, "_");
|
|
50
|
+
const toolIdentifier =
|
|
51
|
+
registeredTools.get(snakeBase) || `${toolPrefix}_${snakeBase}`;
|
|
48
52
|
|
|
49
|
-
// Detect handler function
|
|
50
|
-
const handlerMatch =
|
|
51
|
-
const
|
|
53
|
+
// Detect handler function (both sync and async) and its 1-indexed line number
|
|
54
|
+
const handlerMatch = /export\s+(?:async\s+)?function\s+([a-zA-Z0-9_]+)/.exec(content);
|
|
55
|
+
const classMatch = /export\s+class\s+([A-Za-z0-9_]+)/.exec(content);
|
|
56
|
+
const actionName = handlerMatch ? handlerMatch[1] : `handle_${snakeBase}`;
|
|
57
|
+
const controllerClass = classMatch ? classMatch[1] : baseName;
|
|
58
|
+
const handlerLine =
|
|
59
|
+
handlerMatch && handlerMatch.index !== undefined
|
|
60
|
+
? content.slice(0, handlerMatch.index).split("\n").length
|
|
61
|
+
: 1;
|
|
52
62
|
|
|
53
|
-
// Detect args interface
|
|
54
|
-
const argsMatch =
|
|
63
|
+
// Detect args interface and its line number
|
|
64
|
+
const argsMatch = /(?:export\s+)?interface\s+([a-zA-Z0-9_]+Args)\b/.exec(content);
|
|
55
65
|
const formRequestClass = argsMatch ? argsMatch[1] : null;
|
|
66
|
+
const argsLine =
|
|
67
|
+
argsMatch && argsMatch.index !== undefined
|
|
68
|
+
? content.slice(0, argsMatch.index).split("\n").length
|
|
69
|
+
: undefined;
|
|
56
70
|
|
|
57
|
-
// Detect
|
|
58
|
-
|
|
59
|
-
if (content.includes("SeptumRepository")) modelClass = "SeptumRepository";
|
|
60
|
-
else if (content.includes("SessionManager")) modelClass = "SessionManager";
|
|
61
|
-
else if (content.includes("ConfigLoader")) modelClass = "ConfigLoader";
|
|
71
|
+
// Detect service/repository dependency and its actual file path
|
|
72
|
+
const serviceDep = this.resolveServiceDependency(projectRoot, fullPath, content);
|
|
62
73
|
|
|
63
|
-
const mcpChain: Array<{
|
|
74
|
+
const mcpChain: Array<{
|
|
75
|
+
stage: string;
|
|
76
|
+
symbol: string;
|
|
77
|
+
file?: string;
|
|
78
|
+
line?: number;
|
|
79
|
+
description?: string;
|
|
80
|
+
}> = [
|
|
64
81
|
{
|
|
65
82
|
stage: "ingress",
|
|
66
83
|
symbol: `tool:${toolIdentifier}`,
|
|
67
84
|
file: relPath,
|
|
85
|
+
line: handlerLine,
|
|
68
86
|
description: `MCP Tool Endpoint: ${toolIdentifier}`,
|
|
69
87
|
},
|
|
70
88
|
];
|
|
@@ -74,23 +92,24 @@ export class McpCliSemanticExtractor implements SemanticSliceExtractor {
|
|
|
74
92
|
stage: "validation",
|
|
75
93
|
symbol: formRequestClass,
|
|
76
94
|
file: relPath,
|
|
95
|
+
...(argsLine ? { line: argsLine } : {}),
|
|
77
96
|
description: "MCP Argument Schema",
|
|
78
97
|
});
|
|
79
98
|
}
|
|
80
99
|
|
|
81
100
|
mcpChain.push({
|
|
82
101
|
stage: "controller",
|
|
83
|
-
symbol:
|
|
102
|
+
symbol: classMatch ? `${controllerClass}::${actionName}` : actionName,
|
|
84
103
|
file: relPath,
|
|
85
|
-
line:
|
|
86
|
-
description:
|
|
104
|
+
line: handlerLine,
|
|
105
|
+
description: "Tool Handler Execution",
|
|
87
106
|
});
|
|
88
107
|
|
|
89
|
-
if (
|
|
108
|
+
if (serviceDep) {
|
|
90
109
|
mcpChain.push({
|
|
91
110
|
stage: "service",
|
|
92
|
-
symbol:
|
|
93
|
-
file:
|
|
111
|
+
symbol: serviceDep.symbol,
|
|
112
|
+
...(serviceDep.file ? { file: serviceDep.file } : {}),
|
|
94
113
|
description: "Core Engine Service",
|
|
95
114
|
});
|
|
96
115
|
}
|
|
@@ -101,10 +120,10 @@ export class McpCliSemanticExtractor implements SemanticSliceExtractor {
|
|
|
101
120
|
http_method: "MCP",
|
|
102
121
|
route_uri: `tool:${toolIdentifier}`,
|
|
103
122
|
route_name: toolIdentifier,
|
|
104
|
-
controller_class:
|
|
123
|
+
controller_class: controllerClass,
|
|
105
124
|
action_name: actionName,
|
|
106
125
|
controller_file: relPath,
|
|
107
|
-
controller_line:
|
|
126
|
+
controller_line: handlerLine,
|
|
108
127
|
architecture_style: "rpc",
|
|
109
128
|
entry_kind: "mcp_tool",
|
|
110
129
|
execution_chain_json: JSON.stringify(mcpChain),
|
|
@@ -123,30 +142,44 @@ export class McpCliSemanticExtractor implements SemanticSliceExtractor {
|
|
|
123
142
|
|
|
124
143
|
for (const file of commandFiles) {
|
|
125
144
|
const fullPath = path.join(cliCommandsDir, file);
|
|
126
|
-
const relPath = path.relative(projectRoot, fullPath);
|
|
145
|
+
const relPath = path.relative(projectRoot, fullPath).replace(/\\/g, "/");
|
|
127
146
|
const content = fs.readFileSync(fullPath, "utf-8");
|
|
128
|
-
const commandName = path.basename(file, path.extname(file));
|
|
147
|
+
const commandName = path.basename(file, path.extname(file));
|
|
129
148
|
|
|
130
|
-
const handlerMatch =
|
|
131
|
-
const
|
|
149
|
+
const handlerMatch = /export\s+(?:async\s+)?function\s+([a-zA-Z0-9_]+)/.exec(content);
|
|
150
|
+
const classMatch = /export\s+class\s+([A-Za-z0-9_]+)/.exec(content);
|
|
151
|
+
const actionName = handlerMatch
|
|
152
|
+
? handlerMatch[1]
|
|
153
|
+
: `handle${commandName.charAt(0).toUpperCase() + commandName.slice(1)}Command`;
|
|
154
|
+
const controllerClass = classMatch ? classMatch[1] : commandName;
|
|
155
|
+
const handlerLine =
|
|
156
|
+
handlerMatch && handlerMatch.index !== undefined
|
|
157
|
+
? content.slice(0, handlerMatch.index).split("\n").length
|
|
158
|
+
: 1;
|
|
132
159
|
|
|
133
160
|
// Detect options / args
|
|
134
|
-
const optionsMatch =
|
|
161
|
+
const optionsMatch = /(?:export\s+)?interface\s+([a-zA-Z0-9_]+(?:Options|Args))\b/.exec(content);
|
|
135
162
|
const formRequestClass = optionsMatch ? optionsMatch[1] : null;
|
|
163
|
+
const optionsLine =
|
|
164
|
+
optionsMatch && optionsMatch.index !== undefined
|
|
165
|
+
? content.slice(0, optionsMatch.index).split("\n").length
|
|
166
|
+
: undefined;
|
|
136
167
|
|
|
137
|
-
|
|
138
|
-
let modelClass: string | null = null;
|
|
139
|
-
if (content.includes("IngestionPipeline")) modelClass = "IngestionPipeline";
|
|
140
|
-
else if (content.includes("BoundaryEvaluator")) modelClass = "BoundaryEvaluator";
|
|
141
|
-
else if (content.includes("VerticalSliceTracer")) modelClass = "VerticalSliceTracer";
|
|
142
|
-
else if (content.includes("SeptumRepository")) modelClass = "SeptumRepository";
|
|
168
|
+
const serviceDep = this.resolveServiceDependency(projectRoot, fullPath, content);
|
|
143
169
|
|
|
144
|
-
const cliChain: Array<{
|
|
170
|
+
const cliChain: Array<{
|
|
171
|
+
stage: string;
|
|
172
|
+
symbol: string;
|
|
173
|
+
file?: string;
|
|
174
|
+
line?: number;
|
|
175
|
+
description?: string;
|
|
176
|
+
}> = [
|
|
145
177
|
{
|
|
146
178
|
stage: "ingress",
|
|
147
|
-
symbol:
|
|
179
|
+
symbol: `${cliBinName} ${commandName}`,
|
|
148
180
|
file: relPath,
|
|
149
|
-
|
|
181
|
+
line: handlerLine,
|
|
182
|
+
description: `CLI Command Entrypoint: ${cliBinName} ${commandName}`,
|
|
150
183
|
},
|
|
151
184
|
];
|
|
152
185
|
|
|
@@ -155,23 +188,24 @@ export class McpCliSemanticExtractor implements SemanticSliceExtractor {
|
|
|
155
188
|
stage: "validation",
|
|
156
189
|
symbol: formRequestClass,
|
|
157
190
|
file: relPath,
|
|
191
|
+
...(optionsLine ? { line: optionsLine } : {}),
|
|
158
192
|
description: "CLI Options Interface",
|
|
159
193
|
});
|
|
160
194
|
}
|
|
161
195
|
|
|
162
196
|
cliChain.push({
|
|
163
197
|
stage: "controller",
|
|
164
|
-
symbol:
|
|
198
|
+
symbol: classMatch ? `${controllerClass}::${actionName}` : actionName,
|
|
165
199
|
file: relPath,
|
|
166
|
-
line:
|
|
200
|
+
line: handlerLine,
|
|
167
201
|
description: "Command Execution Handler",
|
|
168
202
|
});
|
|
169
203
|
|
|
170
|
-
if (
|
|
204
|
+
if (serviceDep) {
|
|
171
205
|
cliChain.push({
|
|
172
206
|
stage: "service",
|
|
173
|
-
symbol:
|
|
174
|
-
file:
|
|
207
|
+
symbol: serviceDep.symbol,
|
|
208
|
+
...(serviceDep.file ? { file: serviceDep.file } : {}),
|
|
175
209
|
description: "Core Engine Service",
|
|
176
210
|
});
|
|
177
211
|
}
|
|
@@ -181,11 +215,11 @@ export class McpCliSemanticExtractor implements SemanticSliceExtractor {
|
|
|
181
215
|
feature_key: `cli.${commandName}`,
|
|
182
216
|
http_method: "CLI",
|
|
183
217
|
route_uri: `command:${commandName}`,
|
|
184
|
-
route_name:
|
|
185
|
-
controller_class:
|
|
218
|
+
route_name: `${cliBinName} ${commandName}`,
|
|
219
|
+
controller_class: controllerClass,
|
|
186
220
|
action_name: actionName,
|
|
187
221
|
controller_file: relPath,
|
|
188
|
-
controller_line:
|
|
222
|
+
controller_line: handlerLine,
|
|
189
223
|
architecture_style: "cli",
|
|
190
224
|
entry_kind: "cli_command",
|
|
191
225
|
execution_chain_json: JSON.stringify(cliChain),
|
|
@@ -196,4 +230,99 @@ export class McpCliSemanticExtractor implements SemanticSliceExtractor {
|
|
|
196
230
|
|
|
197
231
|
return slices;
|
|
198
232
|
}
|
|
233
|
+
|
|
234
|
+
private resolveProjectNaming(projectRoot: string): { toolPrefix: string; cliBinName: string } {
|
|
235
|
+
const pkgPath = path.join(projectRoot, "package.json");
|
|
236
|
+
if (fs.existsSync(pkgPath)) {
|
|
237
|
+
try {
|
|
238
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
239
|
+
const rawName = typeof pkg.name === "string" ? pkg.name.replace(/^@[^/]+\//, "") : "mcp";
|
|
240
|
+
const toolPrefix = rawName.replace(/[^a-zA-Z0-9_]/g, "_").toLowerCase() || "mcp";
|
|
241
|
+
let cliBinName = rawName;
|
|
242
|
+
if (typeof pkg.bin === "string") {
|
|
243
|
+
cliBinName = rawName;
|
|
244
|
+
} else if (pkg.bin && typeof pkg.bin === "object") {
|
|
245
|
+
const firstBin = Object.keys(pkg.bin)[0];
|
|
246
|
+
if (firstBin) cliBinName = firstBin;
|
|
247
|
+
}
|
|
248
|
+
return { toolPrefix, cliBinName };
|
|
249
|
+
} catch {}
|
|
250
|
+
}
|
|
251
|
+
return { toolPrefix: "mcp", cliBinName: "cli" };
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
private scanRegisteredMcpToolNames(projectRoot: string): Map<string, string> {
|
|
255
|
+
const map = new Map<string, string>();
|
|
256
|
+
const serverPath = path.join(projectRoot, "src/mcp/server.ts");
|
|
257
|
+
if (!fs.existsSync(serverPath)) return map;
|
|
258
|
+
|
|
259
|
+
try {
|
|
260
|
+
const content = fs.readFileSync(serverPath, "utf-8");
|
|
261
|
+
const nameRegex = /name:\s*['"]([a-zA-Z0-9_-]+)['"]/g;
|
|
262
|
+
let match: RegExpExecArray | null;
|
|
263
|
+
while ((match = nameRegex.exec(content)) !== null) {
|
|
264
|
+
const fullToolName = match[1];
|
|
265
|
+
const normalized = fullToolName.replace(/-/g, "_");
|
|
266
|
+
map.set(normalized, fullToolName);
|
|
267
|
+
const withoutPrefix = normalized.replace(/^[a-z0-9]+_/, "");
|
|
268
|
+
if (withoutPrefix) {
|
|
269
|
+
map.set(withoutPrefix, fullToolName);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
} catch {}
|
|
273
|
+
return map;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
private resolveServiceDependency(
|
|
277
|
+
projectRoot: string,
|
|
278
|
+
sourceFile: string,
|
|
279
|
+
content: string
|
|
280
|
+
): { symbol: string; file?: string } | null {
|
|
281
|
+
const importRegex = /import\s+(?:type\s+)?\{([^}]+)\}\s+from\s+['"]([^'"]+)['"]/g;
|
|
282
|
+
let match: RegExpExecArray | null;
|
|
283
|
+
|
|
284
|
+
while ((match = importRegex.exec(content)) !== null) {
|
|
285
|
+
const clause = match[1];
|
|
286
|
+
const spec = match[2];
|
|
287
|
+
const identifiers = clause
|
|
288
|
+
.split(",")
|
|
289
|
+
.map((s) => s.trim().split(/\s+as\s+/)[0].trim())
|
|
290
|
+
.filter(Boolean);
|
|
291
|
+
|
|
292
|
+
for (const id of identifiers) {
|
|
293
|
+
if (
|
|
294
|
+
/(?:Repository|Service|Manager|Loader|Pipeline|Evaluator|Tracer|Detector|Engine|Store)$/.test(
|
|
295
|
+
id
|
|
296
|
+
)
|
|
297
|
+
) {
|
|
298
|
+
const resolvedFile = this.resolveImportFile(projectRoot, sourceFile, spec);
|
|
299
|
+
return {
|
|
300
|
+
symbol: id,
|
|
301
|
+
...(resolvedFile ? { file: resolvedFile } : {}),
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
return null;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
private resolveImportFile(projectRoot: string, sourceFile: string, importSpec: string): string | undefined {
|
|
311
|
+
if (!importSpec.startsWith(".")) return undefined;
|
|
312
|
+
const baseResolved = path.resolve(path.dirname(sourceFile), importSpec);
|
|
313
|
+
const candidates = [
|
|
314
|
+
baseResolved,
|
|
315
|
+
`${baseResolved}.ts`,
|
|
316
|
+
`${baseResolved}.js`,
|
|
317
|
+
baseResolved.replace(/\.js$/, ".ts"),
|
|
318
|
+
path.join(baseResolved, "index.ts"),
|
|
319
|
+
];
|
|
320
|
+
for (const c of candidates) {
|
|
321
|
+
if (fs.existsSync(c) && fs.statSync(c).isFile()) {
|
|
322
|
+
return path.relative(projectRoot, c).replace(/\\/g, "/");
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
return undefined;
|
|
326
|
+
}
|
|
199
327
|
}
|
|
328
|
+
|
|
@@ -31,18 +31,27 @@ export class NestJsSemanticExtractor implements SemanticSliceExtractor {
|
|
|
31
31
|
const controllerFiles = this.findFilesRecursively(srcDir, /\.controller\.(ts|js)$/);
|
|
32
32
|
|
|
33
33
|
for (const file of controllerFiles) {
|
|
34
|
-
const relPath = path.relative(projectRoot, file);
|
|
34
|
+
const relPath = path.relative(projectRoot, file).replace(/\\/g, "/");
|
|
35
35
|
const content = fs.readFileSync(file, "utf-8");
|
|
36
36
|
|
|
37
37
|
// Extract @Controller('prefix')
|
|
38
|
-
const controllerMatch =
|
|
38
|
+
const controllerMatch = /@Controller\s*\(\s*['"]?([^'")]*)['"]?\s*\)[\s\S]*?export\s+class\s+([A-Za-z0-9_]+)/.exec(
|
|
39
|
+
content
|
|
40
|
+
);
|
|
39
41
|
if (!controllerMatch) continue;
|
|
40
42
|
|
|
41
43
|
const routePrefix = controllerMatch[1] ? `/${controllerMatch[1].replace(/^\/+|\/+$/g, "")}` : "";
|
|
42
44
|
const controllerClass = controllerMatch[2];
|
|
45
|
+
const controllerClassLine =
|
|
46
|
+
controllerMatch.index !== undefined
|
|
47
|
+
? content
|
|
48
|
+
.slice(0, controllerMatch.index + controllerMatch[0].lastIndexOf(controllerClass))
|
|
49
|
+
.split("\n").length
|
|
50
|
+
: 1;
|
|
43
51
|
|
|
44
52
|
// Extract methods: @Get('path'), @Post('path'), etc.
|
|
45
|
-
const methodRegex =
|
|
53
|
+
const methodRegex =
|
|
54
|
+
/@(Get|Post|Put|Patch|Delete)\s*\(\s*['"]?([^'")]*)['"]?\s*\)\s*(?:@[A-Za-z0-9_]+\s*\([^)]*\)\s*)*\s*(?:(?:public|protected|private)\s+)?(?:async\s+)?([a-zA-Z0-9_]+)\s*\(([^)]*)\)/g;
|
|
46
55
|
let match: RegExpExecArray | null;
|
|
47
56
|
|
|
48
57
|
while ((match = methodRegex.exec(content)) !== null) {
|
|
@@ -52,6 +61,10 @@ export class NestJsSemanticExtractor implements SemanticSliceExtractor {
|
|
|
52
61
|
const actionName = match[3];
|
|
53
62
|
const params = match[4];
|
|
54
63
|
|
|
64
|
+
const decoratorLine = content.slice(0, match.index).split("\n").length;
|
|
65
|
+
const actionOffset = match.index + match[0].lastIndexOf(actionName);
|
|
66
|
+
const actionLine = content.slice(0, actionOffset).split("\n").length || controllerClassLine;
|
|
67
|
+
|
|
55
68
|
// Extract DTO from parameters: e.g. @Body() createDto: CreateUserDto
|
|
56
69
|
const dtoMatch = params.match(/@Body\s*\([^)]*\)\s*[a-zA-Z0-9_]+\s*:\s*([A-Za-z0-9_]+Dto)/);
|
|
57
70
|
const formRequestClass = dtoMatch ? dtoMatch[1] : null;
|
|
@@ -61,6 +74,7 @@ export class NestJsSemanticExtractor implements SemanticSliceExtractor {
|
|
|
61
74
|
stage: "ingress",
|
|
62
75
|
symbol: `${httpMethod} ${fullUri}`,
|
|
63
76
|
file: relPath,
|
|
77
|
+
line: decoratorLine,
|
|
64
78
|
description: `NestJS Route: ${httpMethod} ${fullUri}`,
|
|
65
79
|
},
|
|
66
80
|
];
|
|
@@ -77,7 +91,7 @@ export class NestJsSemanticExtractor implements SemanticSliceExtractor {
|
|
|
77
91
|
stage: "controller",
|
|
78
92
|
symbol: `${controllerClass}::${actionName}`,
|
|
79
93
|
file: relPath,
|
|
80
|
-
line:
|
|
94
|
+
line: actionLine,
|
|
81
95
|
description: `NestJS Controller Action`,
|
|
82
96
|
});
|
|
83
97
|
|
|
@@ -90,7 +104,7 @@ export class NestJsSemanticExtractor implements SemanticSliceExtractor {
|
|
|
90
104
|
controller_class: controllerClass,
|
|
91
105
|
action_name: actionName,
|
|
92
106
|
controller_file: relPath,
|
|
93
|
-
controller_line:
|
|
107
|
+
controller_line: actionLine,
|
|
94
108
|
architecture_style: "mvc",
|
|
95
109
|
entry_kind: "http_route",
|
|
96
110
|
execution_chain_json: JSON.stringify(chain),
|
|
@@ -98,6 +112,7 @@ export class NestJsSemanticExtractor implements SemanticSliceExtractor {
|
|
|
98
112
|
}
|
|
99
113
|
}
|
|
100
114
|
|
|
115
|
+
|
|
101
116
|
return slices;
|
|
102
117
|
}
|
|
103
118
|
|