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
|
@@ -286,3 +286,148 @@ export function calculatePythonNesting(
|
|
|
286
286
|
|
|
287
287
|
return maxDepth;
|
|
288
288
|
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Replaces the contents of comments and string/template literals with spaces while
|
|
292
|
+
* preserving exact character offsets, outer quote delimiters, and line breaks.
|
|
293
|
+
*/
|
|
294
|
+
export function blankCommentsAndStrings(
|
|
295
|
+
content: string,
|
|
296
|
+
options?: { hashComments?: boolean }
|
|
297
|
+
): string {
|
|
298
|
+
const hashComments = options?.hashComments ?? false;
|
|
299
|
+
const out = content.split("");
|
|
300
|
+
const len = out.length;
|
|
301
|
+
|
|
302
|
+
let i = 0;
|
|
303
|
+
let inSingle = false;
|
|
304
|
+
let inDouble = false;
|
|
305
|
+
let inTemplate = false;
|
|
306
|
+
let inLineComment = false;
|
|
307
|
+
let inBlockComment = false;
|
|
308
|
+
|
|
309
|
+
while (i < len) {
|
|
310
|
+
const ch = out[i];
|
|
311
|
+
const next = i + 1 < len ? out[i + 1] : "";
|
|
312
|
+
|
|
313
|
+
if (inLineComment) {
|
|
314
|
+
if (ch === "\n") {
|
|
315
|
+
inLineComment = false;
|
|
316
|
+
} else if (ch !== "\r") {
|
|
317
|
+
out[i] = " ";
|
|
318
|
+
}
|
|
319
|
+
i++;
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
if (inBlockComment) {
|
|
324
|
+
if (ch === "*" && next === "/") {
|
|
325
|
+
out[i] = " ";
|
|
326
|
+
out[i + 1] = " ";
|
|
327
|
+
inBlockComment = false;
|
|
328
|
+
i += 2;
|
|
329
|
+
continue;
|
|
330
|
+
}
|
|
331
|
+
if (ch !== "\n" && ch !== "\r") {
|
|
332
|
+
out[i] = " ";
|
|
333
|
+
}
|
|
334
|
+
i++;
|
|
335
|
+
continue;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
if (inSingle) {
|
|
339
|
+
if (ch === "\\" && i + 1 < len) {
|
|
340
|
+
if (out[i] !== "\n" && out[i] !== "\r") out[i] = " ";
|
|
341
|
+
if (out[i + 1] !== "\n" && out[i + 1] !== "\r") out[i + 1] = " ";
|
|
342
|
+
i += 2;
|
|
343
|
+
continue;
|
|
344
|
+
}
|
|
345
|
+
if (ch === "'") {
|
|
346
|
+
inSingle = false;
|
|
347
|
+
} else if (ch !== "\n" && ch !== "\r") {
|
|
348
|
+
out[i] = " ";
|
|
349
|
+
}
|
|
350
|
+
i++;
|
|
351
|
+
continue;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
if (inDouble) {
|
|
355
|
+
if (ch === "\\" && i + 1 < len) {
|
|
356
|
+
if (out[i] !== "\n" && out[i] !== "\r") out[i] = " ";
|
|
357
|
+
if (out[i + 1] !== "\n" && out[i + 1] !== "\r") out[i + 1] = " ";
|
|
358
|
+
i += 2;
|
|
359
|
+
continue;
|
|
360
|
+
}
|
|
361
|
+
if (ch === '"') {
|
|
362
|
+
inDouble = false;
|
|
363
|
+
} else if (ch !== "\n" && ch !== "\r") {
|
|
364
|
+
out[i] = " ";
|
|
365
|
+
}
|
|
366
|
+
i++;
|
|
367
|
+
continue;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
if (inTemplate) {
|
|
371
|
+
if (ch === "\\" && i + 1 < len) {
|
|
372
|
+
if (out[i] !== "\n" && out[i] !== "\r") out[i] = " ";
|
|
373
|
+
if (out[i + 1] !== "\n" && out[i + 1] !== "\r") out[i + 1] = " ";
|
|
374
|
+
i += 2;
|
|
375
|
+
continue;
|
|
376
|
+
}
|
|
377
|
+
if (ch === "`") {
|
|
378
|
+
inTemplate = false;
|
|
379
|
+
} else if (ch !== "\n" && ch !== "\r") {
|
|
380
|
+
out[i] = " ";
|
|
381
|
+
}
|
|
382
|
+
i++;
|
|
383
|
+
continue;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// Normal code state
|
|
387
|
+
if (ch === "/" && next === "/") {
|
|
388
|
+
out[i] = " ";
|
|
389
|
+
out[i + 1] = " ";
|
|
390
|
+
inLineComment = true;
|
|
391
|
+
i += 2;
|
|
392
|
+
continue;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
if (ch === "/" && next === "*") {
|
|
396
|
+
out[i] = " ";
|
|
397
|
+
out[i + 1] = " ";
|
|
398
|
+
inBlockComment = true;
|
|
399
|
+
i += 2;
|
|
400
|
+
continue;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
if (hashComments && ch === "#") {
|
|
404
|
+
out[i] = " ";
|
|
405
|
+
inLineComment = true;
|
|
406
|
+
i++;
|
|
407
|
+
continue;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
if (ch === "'") {
|
|
411
|
+
inSingle = true;
|
|
412
|
+
i++;
|
|
413
|
+
continue;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
if (ch === '"') {
|
|
417
|
+
inDouble = true;
|
|
418
|
+
i++;
|
|
419
|
+
continue;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
if (ch === "`") {
|
|
423
|
+
inTemplate = true;
|
|
424
|
+
i++;
|
|
425
|
+
continue;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
i++;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
return out.join("");
|
|
432
|
+
}
|
|
433
|
+
|
|
@@ -4,8 +4,11 @@ import type {
|
|
|
4
4
|
ParsedFileAST,
|
|
5
5
|
} from "../../../types/index.ts";
|
|
6
6
|
import type { CodeExtractor } from "./base.ts";
|
|
7
|
+
import { TypeScriptExtractor } from "./typescript.ts";
|
|
7
8
|
|
|
8
9
|
export class FrontendExtractor implements CodeExtractor {
|
|
10
|
+
private readonly tsExtractor = new TypeScriptExtractor();
|
|
11
|
+
|
|
9
12
|
public canHandle(filePath: string): boolean {
|
|
10
13
|
return /\.(vue|astro|svelte)$/i.test(filePath);
|
|
11
14
|
}
|
|
@@ -19,22 +22,21 @@ export class FrontendExtractor implements CodeExtractor {
|
|
|
19
22
|
const isSvelte = /\.svelte$/i.test(filePath);
|
|
20
23
|
|
|
21
24
|
if (isVue) {
|
|
22
|
-
this.extractVue(content, symbols, dependencies);
|
|
25
|
+
await this.extractVue(content, symbols, dependencies);
|
|
23
26
|
} else if (isAstro) {
|
|
24
|
-
this.extractAstro(content, symbols, dependencies);
|
|
27
|
+
await this.extractAstro(content, symbols, dependencies);
|
|
25
28
|
} else if (isSvelte) {
|
|
26
|
-
this.extractSvelte(content, symbols, dependencies);
|
|
29
|
+
await this.extractSvelte(content, symbols, dependencies);
|
|
27
30
|
}
|
|
28
31
|
|
|
29
32
|
return { symbols, dependencies };
|
|
30
33
|
}
|
|
31
34
|
|
|
32
|
-
private extractVue(
|
|
35
|
+
private async extractVue(
|
|
33
36
|
content: string,
|
|
34
37
|
symbols: ExtractedSymbol[],
|
|
35
38
|
dependencies: ExtractedDependency[]
|
|
36
|
-
): void {
|
|
37
|
-
// Match <script> and <script setup> blocks
|
|
39
|
+
): Promise<void> {
|
|
38
40
|
const scriptRegex = /<script(\s+[^>]*?)?>([\s\S]*?)<\/script>/gi;
|
|
39
41
|
let match: RegExpExecArray | null;
|
|
40
42
|
|
|
@@ -44,64 +46,61 @@ export class FrontendExtractor implements CodeExtractor {
|
|
|
44
46
|
const scriptBody = match[2];
|
|
45
47
|
const isSetup = /\bsetup\b/i.test(attributes);
|
|
46
48
|
|
|
47
|
-
// Calculate start line of the script body
|
|
48
|
-
const beforeMatch = content.slice(0, match.index);
|
|
49
49
|
const tagOpenMatch = fullMatch.match(/<script(\s+[^>]*?)?>/i);
|
|
50
50
|
const tagOpenLen = tagOpenMatch ? tagOpenMatch[0].length : 0;
|
|
51
51
|
const scriptStartOffset = match.index + tagOpenLen;
|
|
52
52
|
const startLine = content.slice(0, scriptStartOffset).split("\n").length;
|
|
53
53
|
|
|
54
|
-
|
|
55
|
-
this.extractScriptContent(scriptBody, startLine, symbols, dependencies);
|
|
54
|
+
await this.extractScriptContent(scriptBody, startLine, symbols, dependencies);
|
|
56
55
|
|
|
57
56
|
if (isSetup) {
|
|
57
|
+
const scriptLines = scriptBody.split("\n").length;
|
|
58
58
|
symbols.push({
|
|
59
59
|
name: "<script setup>",
|
|
60
60
|
kind: "method",
|
|
61
61
|
signature: "script setup",
|
|
62
62
|
visibility: "public",
|
|
63
63
|
line_start: startLine,
|
|
64
|
-
line_end: startLine +
|
|
65
|
-
line_count:
|
|
64
|
+
line_end: startLine + scriptLines - 1,
|
|
65
|
+
line_count: Math.max(1, scriptLines),
|
|
66
66
|
});
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
// Vue defineProps / defineEmits detection
|
|
71
70
|
const definePropsMatch = content.match(/defineProps\s*<([^>]+)>/);
|
|
72
71
|
if (definePropsMatch) {
|
|
73
72
|
const line = content.slice(0, content.indexOf(definePropsMatch[0])).split("\n").length;
|
|
73
|
+
const propLines = definePropsMatch[0].split("\n").length;
|
|
74
74
|
symbols.push({
|
|
75
75
|
name: "Props",
|
|
76
76
|
kind: "interface",
|
|
77
77
|
signature: `defineProps<${definePropsMatch[1]}>`,
|
|
78
78
|
visibility: "public",
|
|
79
79
|
line_start: line,
|
|
80
|
-
line_end: line,
|
|
81
|
-
line_count: 1,
|
|
80
|
+
line_end: line + propLines - 1,
|
|
81
|
+
line_count: Math.max(1, propLines),
|
|
82
82
|
});
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
-
private extractAstro(
|
|
86
|
+
private async extractAstro(
|
|
87
87
|
content: string,
|
|
88
88
|
symbols: ExtractedSymbol[],
|
|
89
89
|
dependencies: ExtractedDependency[]
|
|
90
|
-
): void {
|
|
91
|
-
// Astro frontmatter is between the first --- and the second ---
|
|
90
|
+
): Promise<void> {
|
|
92
91
|
const frontmatterMatch = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
93
92
|
if (frontmatterMatch) {
|
|
94
93
|
const scriptBody = frontmatterMatch[1];
|
|
95
|
-
const startLine = 2;
|
|
96
|
-
this.extractScriptContent(scriptBody, startLine, symbols, dependencies);
|
|
94
|
+
const startLine = 2;
|
|
95
|
+
await this.extractScriptContent(scriptBody, startLine, symbols, dependencies);
|
|
97
96
|
}
|
|
98
97
|
}
|
|
99
98
|
|
|
100
|
-
private extractSvelte(
|
|
99
|
+
private async extractSvelte(
|
|
101
100
|
content: string,
|
|
102
101
|
symbols: ExtractedSymbol[],
|
|
103
102
|
dependencies: ExtractedDependency[]
|
|
104
|
-
): void {
|
|
103
|
+
): Promise<void> {
|
|
105
104
|
const scriptRegex = /<script(\s+[^>]*?)?>([\s\S]*?)<\/script>/gi;
|
|
106
105
|
let match: RegExpExecArray | null;
|
|
107
106
|
|
|
@@ -113,82 +112,36 @@ export class FrontendExtractor implements CodeExtractor {
|
|
|
113
112
|
const scriptStartOffset = match.index + tagOpenLen;
|
|
114
113
|
const startLine = content.slice(0, scriptStartOffset).split("\n").length;
|
|
115
114
|
|
|
116
|
-
this.extractScriptContent(scriptBody, startLine, symbols, dependencies);
|
|
115
|
+
await this.extractScriptContent(scriptBody, startLine, symbols, dependencies);
|
|
117
116
|
}
|
|
118
117
|
}
|
|
119
118
|
|
|
120
|
-
private extractScriptContent(
|
|
119
|
+
private async extractScriptContent(
|
|
121
120
|
scriptContent: string,
|
|
122
|
-
|
|
121
|
+
startLine: number,
|
|
123
122
|
symbols: ExtractedSymbol[],
|
|
124
123
|
dependencies: ExtractedDependency[]
|
|
125
|
-
): void {
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
const target = match[1] || match[2];
|
|
135
|
-
if (target) {
|
|
136
|
-
const isExternal = !target.startsWith(".") && !target.startsWith("/");
|
|
137
|
-
dependencies.push({
|
|
138
|
-
target,
|
|
139
|
-
statement: line.trim(),
|
|
140
|
-
line_number: lineOffset + i,
|
|
141
|
-
is_external: isExternal,
|
|
142
|
-
});
|
|
143
|
-
}
|
|
144
|
-
}
|
|
124
|
+
): Promise<void> {
|
|
125
|
+
const parsed = await this.tsExtractor.extract("component-script.ts", scriptContent);
|
|
126
|
+
const delta = Math.max(0, startLine - 1);
|
|
127
|
+
|
|
128
|
+
for (const dep of parsed.dependencies) {
|
|
129
|
+
dependencies.push({
|
|
130
|
+
...dep,
|
|
131
|
+
line_number: dep.line_number + delta,
|
|
132
|
+
});
|
|
145
133
|
}
|
|
146
134
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
let m: RegExpExecArray | null;
|
|
157
|
-
while ((m = funcRegex.exec(line)) !== null) {
|
|
158
|
-
symbols.push({
|
|
159
|
-
name: m[1],
|
|
160
|
-
kind: "function",
|
|
161
|
-
signature: line.trim(),
|
|
162
|
-
visibility: line.includes("export") ? "public" : "private",
|
|
163
|
-
line_start: currentLineNum,
|
|
164
|
-
line_end: currentLineNum,
|
|
165
|
-
line_count: 1,
|
|
166
|
-
});
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
while ((m = constFuncRegex.exec(line)) !== null) {
|
|
170
|
-
symbols.push({
|
|
171
|
-
name: m[1],
|
|
172
|
-
kind: "function",
|
|
173
|
-
signature: line.trim(),
|
|
174
|
-
visibility: line.includes("export") ? "public" : "private",
|
|
175
|
-
line_start: currentLineNum,
|
|
176
|
-
line_end: currentLineNum,
|
|
177
|
-
line_count: 1,
|
|
178
|
-
});
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
while ((m = classRegex.exec(line)) !== null) {
|
|
182
|
-
symbols.push({
|
|
183
|
-
name: m[1],
|
|
184
|
-
kind: "class",
|
|
185
|
-
signature: line.trim(),
|
|
186
|
-
visibility: line.includes("export") ? "public" : "private",
|
|
187
|
-
line_start: currentLineNum,
|
|
188
|
-
line_end: currentLineNum,
|
|
189
|
-
line_count: 1,
|
|
190
|
-
});
|
|
191
|
-
}
|
|
135
|
+
for (const sym of parsed.symbols) {
|
|
136
|
+
const shiftedStart = sym.line_start + delta;
|
|
137
|
+
const shiftedEnd = Math.max(shiftedStart, sym.line_end + delta);
|
|
138
|
+
symbols.push({
|
|
139
|
+
...sym,
|
|
140
|
+
line_start: shiftedStart,
|
|
141
|
+
line_end: shiftedEnd,
|
|
142
|
+
line_count: Math.max(1, shiftedEnd - shiftedStart + 1),
|
|
143
|
+
});
|
|
192
144
|
}
|
|
193
145
|
}
|
|
194
146
|
}
|
|
147
|
+
|