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.
- package/LICENSE +21 -21
- package/README.md +100 -100
- package/bin/septum.ts +4 -4
- package/package.json +63 -62
- package/src/cli/commands/check.ts +161 -161
- package/src/cli/commands/hook.ts +188 -188
- package/src/cli/commands/ingest.ts +29 -29
- package/src/cli/commands/init.ts +211 -211
- package/src/cli/commands/locate.ts +61 -61
- package/src/cli/commands/query.ts +64 -64
- package/src/cli/commands/serve.ts +5 -5
- package/src/cli/commands/slice.ts +90 -90
- package/src/cli/commands/sync.ts +31 -31
- package/src/cli/index.ts +199 -178
- package/src/cli/templates/hooks.ts +75 -75
- package/src/cli/templates/rules.ts +38 -38
- package/src/cli/templates/skill.ts +66 -66
- package/src/core/boundary/evaluator.ts +210 -210
- package/src/core/config/loader.ts +84 -84
- package/src/core/config/schema.ts +39 -39
- package/src/core/database/client.ts +44 -132
- package/src/core/database/repositories/dependency.repository.ts +279 -279
- package/src/core/database/repositories/domain.repository.ts +198 -198
- package/src/core/database/repositories/file.repository.ts +99 -99
- package/src/core/database/repositories/meta.repository.ts +33 -33
- package/src/core/database/repositories/symbol.repository.ts +424 -424
- package/src/core/database/repository.ts +242 -242
- package/src/core/database/schema.ts +120 -0
- package/src/core/discovery/topology-detector.ts +418 -418
- package/src/core/ingestion/hasher.ts +5 -5
- package/src/core/ingestion/pipeline.ts +438 -438
- package/src/core/parser/boundary-tracker.ts +128 -128
- package/src/core/parser/extractors/base.ts +60 -60
- package/src/core/parser/extractors/frontend.ts +194 -194
- package/src/core/parser/extractors/go.ts +176 -176
- package/src/core/parser/extractors/laravel-semantic.ts +426 -426
- package/src/core/parser/extractors/mcp-cli-semantic.ts +199 -199
- package/src/core/parser/extractors/nestjs-semantic.ts +119 -119
- package/src/core/parser/extractors/php.ts +149 -149
- package/src/core/parser/extractors/python.ts +226 -226
- package/src/core/parser/extractors/registry.ts +49 -49
- package/src/core/parser/extractors/semantic-extractor.interface.ts +21 -21
- package/src/core/parser/extractors/typescript.ts +177 -177
- package/src/core/parser/tree-sitter.ts +39 -39
- package/src/core/resolver/call-graph-tracer.ts +668 -668
- package/src/core/resolver/module-resolver.ts +342 -323
- package/src/core/resolver/symbol-locator.ts +543 -405
- package/src/core/resolver/vertical-slice-tracer.ts +237 -236
- package/src/core/session/session-manager.ts +56 -56
- package/src/core/telemetry/telemetry.ts +86 -86
- package/src/index.ts +18 -18
- package/src/mcp/schemas.ts +68 -68
- package/src/mcp/server.ts +612 -602
- package/src/mcp/tools/check-boundary.ts +102 -102
- package/src/mcp/tools/get-domain-catalog.ts +91 -91
- package/src/mcp/tools/get-feature-context.ts +116 -116
- package/src/mcp/tools/get-symbol-hotspots.ts +49 -49
- package/src/mcp/tools/get-symbol-impact.ts +26 -26
- package/src/mcp/tools/get-symbol.ts +63 -63
- package/src/mcp/tools/locate-symbol.ts +74 -74
- package/src/mcp/tools/register-domain.ts +64 -64
- package/src/mcp/tools/trace-vertical-slice.ts +102 -102
- package/src/types/index.ts +296 -296
- package/src/version.ts +3 -0
- package/src/core/database/schema.sql +0 -92
|
@@ -1,226 +1,226 @@
|
|
|
1
|
-
import type { ExtractedDependency, ExtractedSymbol, ParsedFileAST, Visibility } from "../../../types/index.ts";
|
|
2
|
-
import { findPythonBlockEnd } from "../boundary-tracker.ts";
|
|
3
|
-
import type { CodeExtractor } from "./base.ts";
|
|
4
|
-
|
|
5
|
-
const PYTHON_STDLIB_AND_EXTERNALS = new Set([
|
|
6
|
-
"os",
|
|
7
|
-
"sys",
|
|
8
|
-
"json",
|
|
9
|
-
"math",
|
|
10
|
-
"typing",
|
|
11
|
-
"re",
|
|
12
|
-
"datetime",
|
|
13
|
-
"time",
|
|
14
|
-
"collections",
|
|
15
|
-
"itertools",
|
|
16
|
-
"functools",
|
|
17
|
-
"pathlib",
|
|
18
|
-
"io",
|
|
19
|
-
"logging",
|
|
20
|
-
"subprocess",
|
|
21
|
-
"threading",
|
|
22
|
-
"multiprocessing",
|
|
23
|
-
"asyncio",
|
|
24
|
-
"abc",
|
|
25
|
-
"copy",
|
|
26
|
-
"enum",
|
|
27
|
-
"dataclasses",
|
|
28
|
-
"uuid",
|
|
29
|
-
"hashlib",
|
|
30
|
-
"random",
|
|
31
|
-
"socket",
|
|
32
|
-
"unittest",
|
|
33
|
-
// Common 3rd party frameworks
|
|
34
|
-
"pydantic",
|
|
35
|
-
"fastapi",
|
|
36
|
-
"django",
|
|
37
|
-
"flask",
|
|
38
|
-
"sqlalchemy",
|
|
39
|
-
"celery",
|
|
40
|
-
"numpy",
|
|
41
|
-
"pandas",
|
|
42
|
-
"requests",
|
|
43
|
-
"httpx",
|
|
44
|
-
"pytest",
|
|
45
|
-
"starlette",
|
|
46
|
-
"alembic",
|
|
47
|
-
"redis",
|
|
48
|
-
]);
|
|
49
|
-
|
|
50
|
-
export class PythonExtractor implements CodeExtractor {
|
|
51
|
-
public canHandle(filePath: string): boolean {
|
|
52
|
-
return /\.py$/i.test(filePath);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
public async extract(filePath: string, content: string): Promise<ParsedFileAST> {
|
|
56
|
-
const symbols: ExtractedSymbol[] = [];
|
|
57
|
-
const dependencies: ExtractedDependency[] = [];
|
|
58
|
-
|
|
59
|
-
const lines = content.split("\n");
|
|
60
|
-
|
|
61
|
-
// 1. Extract Python Imports
|
|
62
|
-
for (let i = 0; i < lines.length; i++) {
|
|
63
|
-
const line = lines[i];
|
|
64
|
-
const trimmed = line.trim();
|
|
65
|
-
const lineNum = i + 1;
|
|
66
|
-
|
|
67
|
-
// Skip comments
|
|
68
|
-
if (trimmed.startsWith("#")) continue;
|
|
69
|
-
|
|
70
|
-
// Pattern A: from <module> import <symbols>
|
|
71
|
-
const fromMatch = /^from\s+([.\w]+)\s+import\s+([*\w\s,()]+)/.exec(trimmed);
|
|
72
|
-
if (fromMatch) {
|
|
73
|
-
const modulePath = fromMatch[1];
|
|
74
|
-
const isRelative = modulePath.startsWith(".");
|
|
75
|
-
const topPackage = isRelative ? "" : modulePath.split(".")[0];
|
|
76
|
-
const isExternal = !isRelative && PYTHON_STDLIB_AND_EXTERNALS.has(topPackage);
|
|
77
|
-
|
|
78
|
-
dependencies.push({
|
|
79
|
-
target: modulePath,
|
|
80
|
-
statement: trimmed,
|
|
81
|
-
line_number: lineNum,
|
|
82
|
-
is_external: isExternal,
|
|
83
|
-
});
|
|
84
|
-
continue;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// Pattern B: import <module> [as <alias>]
|
|
88
|
-
const importMatch = /^import\s+([.\w]+)(?:\s+as\s+\w+)?/.exec(trimmed);
|
|
89
|
-
if (importMatch) {
|
|
90
|
-
const modulePath = importMatch[1];
|
|
91
|
-
const topPackage = modulePath.split(".")[0];
|
|
92
|
-
const isExternal = PYTHON_STDLIB_AND_EXTERNALS.has(topPackage);
|
|
93
|
-
|
|
94
|
-
dependencies.push({
|
|
95
|
-
target: modulePath,
|
|
96
|
-
statement: trimmed,
|
|
97
|
-
line_number: lineNum,
|
|
98
|
-
is_external: isExternal,
|
|
99
|
-
});
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// 2. Extract Python Classes and Functions
|
|
104
|
-
let currentClass: string | null = null;
|
|
105
|
-
let currentClassIndent = -1;
|
|
106
|
-
|
|
107
|
-
for (let i = 0; i < lines.length; i++) {
|
|
108
|
-
const line = lines[i];
|
|
109
|
-
const trimmed = line.trim();
|
|
110
|
-
const lineNum = i + 1;
|
|
111
|
-
|
|
112
|
-
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
113
|
-
|
|
114
|
-
const indentMatch = /^(\s*)/.exec(line);
|
|
115
|
-
const indent = indentMatch ? indentMatch[1].length : 0;
|
|
116
|
-
|
|
117
|
-
// Check if we exited current class scope
|
|
118
|
-
if (currentClass !== null && indent <= currentClassIndent) {
|
|
119
|
-
currentClass = null;
|
|
120
|
-
currentClassIndent = -1;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// Class Definition
|
|
124
|
-
const classMatch = /^class\s+([A-Za-z0-9_]+)(?:\s*\([^)]*\))?\s*:/.exec(trimmed);
|
|
125
|
-
if (classMatch) {
|
|
126
|
-
const className = classMatch[1];
|
|
127
|
-
currentClass = className;
|
|
128
|
-
currentClassIndent = indent;
|
|
129
|
-
|
|
130
|
-
const visibility: Visibility = className.startsWith("__")
|
|
131
|
-
? "private"
|
|
132
|
-
: className.startsWith("_")
|
|
133
|
-
? "protected"
|
|
134
|
-
: "public";
|
|
135
|
-
|
|
136
|
-
const endLine = findPythonBlockEnd(lines, i);
|
|
137
|
-
symbols.push({
|
|
138
|
-
name: className,
|
|
139
|
-
kind: "class",
|
|
140
|
-
signature: trimmed.replace(/:$/, "").trim(),
|
|
141
|
-
visibility,
|
|
142
|
-
line_start: lineNum,
|
|
143
|
-
line_end: endLine,
|
|
144
|
-
line_count: Math.max(1, endLine - lineNum + 1),
|
|
145
|
-
});
|
|
146
|
-
continue;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// Class Attributes or Pydantic fields inside class
|
|
150
|
-
if (currentClass !== null && indent > currentClassIndent) {
|
|
151
|
-
const attrMatch = /^([A-Za-z0-9_]+)\s*(?::\s*([^=]+))?(?:\s*=\s*(.+))?$/.exec(trimmed);
|
|
152
|
-
if (
|
|
153
|
-
attrMatch &&
|
|
154
|
-
!trimmed.startsWith("def ") &&
|
|
155
|
-
!trimmed.startsWith("async def ") &&
|
|
156
|
-
!["return", "pass", "raise", "assert", "import", "from", "yield"].includes(attrMatch[1])
|
|
157
|
-
) {
|
|
158
|
-
const attrName = attrMatch[1];
|
|
159
|
-
const visibility: Visibility = attrName.startsWith("__")
|
|
160
|
-
? "private"
|
|
161
|
-
: attrName.startsWith("_")
|
|
162
|
-
? "protected"
|
|
163
|
-
: "public";
|
|
164
|
-
|
|
165
|
-
symbols.push({
|
|
166
|
-
name: `${currentClass}::${attrName}`,
|
|
167
|
-
kind: "property",
|
|
168
|
-
signature: trimmed,
|
|
169
|
-
visibility,
|
|
170
|
-
line_start: lineNum,
|
|
171
|
-
line_end: lineNum,
|
|
172
|
-
line_count: 1,
|
|
173
|
-
});
|
|
174
|
-
continue;
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// Module-level UPPERCASE constants: MAX_RETRIES = 5
|
|
179
|
-
if (currentClass === null && indent === 0) {
|
|
180
|
-
const constMatch = /^([A-Z][A-Z0-9_]*)\s*(?::\s*([^=]+))?\s*=\s*(.+)$/.exec(trimmed);
|
|
181
|
-
if (constMatch) {
|
|
182
|
-
const constName = constMatch[1];
|
|
183
|
-
symbols.push({
|
|
184
|
-
name: constName,
|
|
185
|
-
kind: "property",
|
|
186
|
-
signature: trimmed,
|
|
187
|
-
visibility: "public",
|
|
188
|
-
line_start: lineNum,
|
|
189
|
-
line_end: lineNum,
|
|
190
|
-
line_count: 1,
|
|
191
|
-
});
|
|
192
|
-
continue;
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
// Function or Method Definition
|
|
197
|
-
const funcMatch = /^(?:async\s+)?def\s+([A-Za-z0-9_]+)\s*\(([^)]*)\)(?:\s*->\s*[^:]+)?\s*:/.exec(trimmed);
|
|
198
|
-
if (funcMatch) {
|
|
199
|
-
const funcName = funcMatch[1];
|
|
200
|
-
const isMethod = currentClass !== null && indent > currentClassIndent;
|
|
201
|
-
|
|
202
|
-
const visibility: Visibility = funcName.startsWith("__")
|
|
203
|
-
? "private"
|
|
204
|
-
: funcName.startsWith("_")
|
|
205
|
-
? "protected"
|
|
206
|
-
: "public";
|
|
207
|
-
|
|
208
|
-
const endLine = findPythonBlockEnd(lines, i);
|
|
209
|
-
symbols.push({
|
|
210
|
-
name: isMethod ? `${currentClass}::${funcName}` : funcName,
|
|
211
|
-
kind: isMethod ? "method" : "function",
|
|
212
|
-
signature: trimmed.replace(/:$/, "").trim(),
|
|
213
|
-
visibility,
|
|
214
|
-
line_start: lineNum,
|
|
215
|
-
line_end: endLine,
|
|
216
|
-
line_count: Math.max(1, endLine - lineNum + 1),
|
|
217
|
-
});
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
return {
|
|
222
|
-
symbols,
|
|
223
|
-
dependencies,
|
|
224
|
-
};
|
|
225
|
-
}
|
|
226
|
-
}
|
|
1
|
+
import type { ExtractedDependency, ExtractedSymbol, ParsedFileAST, Visibility } from "../../../types/index.ts";
|
|
2
|
+
import { findPythonBlockEnd } from "../boundary-tracker.ts";
|
|
3
|
+
import type { CodeExtractor } from "./base.ts";
|
|
4
|
+
|
|
5
|
+
const PYTHON_STDLIB_AND_EXTERNALS = new Set([
|
|
6
|
+
"os",
|
|
7
|
+
"sys",
|
|
8
|
+
"json",
|
|
9
|
+
"math",
|
|
10
|
+
"typing",
|
|
11
|
+
"re",
|
|
12
|
+
"datetime",
|
|
13
|
+
"time",
|
|
14
|
+
"collections",
|
|
15
|
+
"itertools",
|
|
16
|
+
"functools",
|
|
17
|
+
"pathlib",
|
|
18
|
+
"io",
|
|
19
|
+
"logging",
|
|
20
|
+
"subprocess",
|
|
21
|
+
"threading",
|
|
22
|
+
"multiprocessing",
|
|
23
|
+
"asyncio",
|
|
24
|
+
"abc",
|
|
25
|
+
"copy",
|
|
26
|
+
"enum",
|
|
27
|
+
"dataclasses",
|
|
28
|
+
"uuid",
|
|
29
|
+
"hashlib",
|
|
30
|
+
"random",
|
|
31
|
+
"socket",
|
|
32
|
+
"unittest",
|
|
33
|
+
// Common 3rd party frameworks
|
|
34
|
+
"pydantic",
|
|
35
|
+
"fastapi",
|
|
36
|
+
"django",
|
|
37
|
+
"flask",
|
|
38
|
+
"sqlalchemy",
|
|
39
|
+
"celery",
|
|
40
|
+
"numpy",
|
|
41
|
+
"pandas",
|
|
42
|
+
"requests",
|
|
43
|
+
"httpx",
|
|
44
|
+
"pytest",
|
|
45
|
+
"starlette",
|
|
46
|
+
"alembic",
|
|
47
|
+
"redis",
|
|
48
|
+
]);
|
|
49
|
+
|
|
50
|
+
export class PythonExtractor implements CodeExtractor {
|
|
51
|
+
public canHandle(filePath: string): boolean {
|
|
52
|
+
return /\.py$/i.test(filePath);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
public async extract(filePath: string, content: string): Promise<ParsedFileAST> {
|
|
56
|
+
const symbols: ExtractedSymbol[] = [];
|
|
57
|
+
const dependencies: ExtractedDependency[] = [];
|
|
58
|
+
|
|
59
|
+
const lines = content.split("\n");
|
|
60
|
+
|
|
61
|
+
// 1. Extract Python Imports
|
|
62
|
+
for (let i = 0; i < lines.length; i++) {
|
|
63
|
+
const line = lines[i];
|
|
64
|
+
const trimmed = line.trim();
|
|
65
|
+
const lineNum = i + 1;
|
|
66
|
+
|
|
67
|
+
// Skip comments
|
|
68
|
+
if (trimmed.startsWith("#")) continue;
|
|
69
|
+
|
|
70
|
+
// Pattern A: from <module> import <symbols>
|
|
71
|
+
const fromMatch = /^from\s+([.\w]+)\s+import\s+([*\w\s,()]+)/.exec(trimmed);
|
|
72
|
+
if (fromMatch) {
|
|
73
|
+
const modulePath = fromMatch[1];
|
|
74
|
+
const isRelative = modulePath.startsWith(".");
|
|
75
|
+
const topPackage = isRelative ? "" : modulePath.split(".")[0];
|
|
76
|
+
const isExternal = !isRelative && PYTHON_STDLIB_AND_EXTERNALS.has(topPackage);
|
|
77
|
+
|
|
78
|
+
dependencies.push({
|
|
79
|
+
target: modulePath,
|
|
80
|
+
statement: trimmed,
|
|
81
|
+
line_number: lineNum,
|
|
82
|
+
is_external: isExternal,
|
|
83
|
+
});
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Pattern B: import <module> [as <alias>]
|
|
88
|
+
const importMatch = /^import\s+([.\w]+)(?:\s+as\s+\w+)?/.exec(trimmed);
|
|
89
|
+
if (importMatch) {
|
|
90
|
+
const modulePath = importMatch[1];
|
|
91
|
+
const topPackage = modulePath.split(".")[0];
|
|
92
|
+
const isExternal = PYTHON_STDLIB_AND_EXTERNALS.has(topPackage);
|
|
93
|
+
|
|
94
|
+
dependencies.push({
|
|
95
|
+
target: modulePath,
|
|
96
|
+
statement: trimmed,
|
|
97
|
+
line_number: lineNum,
|
|
98
|
+
is_external: isExternal,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// 2. Extract Python Classes and Functions
|
|
104
|
+
let currentClass: string | null = null;
|
|
105
|
+
let currentClassIndent = -1;
|
|
106
|
+
|
|
107
|
+
for (let i = 0; i < lines.length; i++) {
|
|
108
|
+
const line = lines[i];
|
|
109
|
+
const trimmed = line.trim();
|
|
110
|
+
const lineNum = i + 1;
|
|
111
|
+
|
|
112
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
113
|
+
|
|
114
|
+
const indentMatch = /^(\s*)/.exec(line);
|
|
115
|
+
const indent = indentMatch ? indentMatch[1].length : 0;
|
|
116
|
+
|
|
117
|
+
// Check if we exited current class scope
|
|
118
|
+
if (currentClass !== null && indent <= currentClassIndent) {
|
|
119
|
+
currentClass = null;
|
|
120
|
+
currentClassIndent = -1;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Class Definition
|
|
124
|
+
const classMatch = /^class\s+([A-Za-z0-9_]+)(?:\s*\([^)]*\))?\s*:/.exec(trimmed);
|
|
125
|
+
if (classMatch) {
|
|
126
|
+
const className = classMatch[1];
|
|
127
|
+
currentClass = className;
|
|
128
|
+
currentClassIndent = indent;
|
|
129
|
+
|
|
130
|
+
const visibility: Visibility = className.startsWith("__")
|
|
131
|
+
? "private"
|
|
132
|
+
: className.startsWith("_")
|
|
133
|
+
? "protected"
|
|
134
|
+
: "public";
|
|
135
|
+
|
|
136
|
+
const endLine = findPythonBlockEnd(lines, i);
|
|
137
|
+
symbols.push({
|
|
138
|
+
name: className,
|
|
139
|
+
kind: "class",
|
|
140
|
+
signature: trimmed.replace(/:$/, "").trim(),
|
|
141
|
+
visibility,
|
|
142
|
+
line_start: lineNum,
|
|
143
|
+
line_end: endLine,
|
|
144
|
+
line_count: Math.max(1, endLine - lineNum + 1),
|
|
145
|
+
});
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Class Attributes or Pydantic fields inside class
|
|
150
|
+
if (currentClass !== null && indent > currentClassIndent) {
|
|
151
|
+
const attrMatch = /^([A-Za-z0-9_]+)\s*(?::\s*([^=]+))?(?:\s*=\s*(.+))?$/.exec(trimmed);
|
|
152
|
+
if (
|
|
153
|
+
attrMatch &&
|
|
154
|
+
!trimmed.startsWith("def ") &&
|
|
155
|
+
!trimmed.startsWith("async def ") &&
|
|
156
|
+
!["return", "pass", "raise", "assert", "import", "from", "yield"].includes(attrMatch[1])
|
|
157
|
+
) {
|
|
158
|
+
const attrName = attrMatch[1];
|
|
159
|
+
const visibility: Visibility = attrName.startsWith("__")
|
|
160
|
+
? "private"
|
|
161
|
+
: attrName.startsWith("_")
|
|
162
|
+
? "protected"
|
|
163
|
+
: "public";
|
|
164
|
+
|
|
165
|
+
symbols.push({
|
|
166
|
+
name: `${currentClass}::${attrName}`,
|
|
167
|
+
kind: "property",
|
|
168
|
+
signature: trimmed,
|
|
169
|
+
visibility,
|
|
170
|
+
line_start: lineNum,
|
|
171
|
+
line_end: lineNum,
|
|
172
|
+
line_count: 1,
|
|
173
|
+
});
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Module-level UPPERCASE constants: MAX_RETRIES = 5
|
|
179
|
+
if (currentClass === null && indent === 0) {
|
|
180
|
+
const constMatch = /^([A-Z][A-Z0-9_]*)\s*(?::\s*([^=]+))?\s*=\s*(.+)$/.exec(trimmed);
|
|
181
|
+
if (constMatch) {
|
|
182
|
+
const constName = constMatch[1];
|
|
183
|
+
symbols.push({
|
|
184
|
+
name: constName,
|
|
185
|
+
kind: "property",
|
|
186
|
+
signature: trimmed,
|
|
187
|
+
visibility: "public",
|
|
188
|
+
line_start: lineNum,
|
|
189
|
+
line_end: lineNum,
|
|
190
|
+
line_count: 1,
|
|
191
|
+
});
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Function or Method Definition
|
|
197
|
+
const funcMatch = /^(?:async\s+)?def\s+([A-Za-z0-9_]+)\s*\(([^)]*)\)(?:\s*->\s*[^:]+)?\s*:/.exec(trimmed);
|
|
198
|
+
if (funcMatch) {
|
|
199
|
+
const funcName = funcMatch[1];
|
|
200
|
+
const isMethod = currentClass !== null && indent > currentClassIndent;
|
|
201
|
+
|
|
202
|
+
const visibility: Visibility = funcName.startsWith("__")
|
|
203
|
+
? "private"
|
|
204
|
+
: funcName.startsWith("_")
|
|
205
|
+
? "protected"
|
|
206
|
+
: "public";
|
|
207
|
+
|
|
208
|
+
const endLine = findPythonBlockEnd(lines, i);
|
|
209
|
+
symbols.push({
|
|
210
|
+
name: isMethod ? `${currentClass}::${funcName}` : funcName,
|
|
211
|
+
kind: isMethod ? "method" : "function",
|
|
212
|
+
signature: trimmed.replace(/:$/, "").trim(),
|
|
213
|
+
visibility,
|
|
214
|
+
line_start: lineNum,
|
|
215
|
+
line_end: endLine,
|
|
216
|
+
line_count: Math.max(1, endLine - lineNum + 1),
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return {
|
|
222
|
+
symbols,
|
|
223
|
+
dependencies,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
}
|
|
@@ -1,49 +1,49 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
SemanticSliceExtractor,
|
|
3
|
-
VerticalSliceCandidate,
|
|
4
|
-
} from "./semantic-extractor.interface.ts";
|
|
5
|
-
import { LaravelSemanticExtractor } from "./laravel-semantic.ts";
|
|
6
|
-
import { McpCliSemanticExtractor } from "./mcp-cli-semantic.ts";
|
|
7
|
-
import { NestJsSemanticExtractor } from "./nestjs-semantic.ts";
|
|
8
|
-
|
|
9
|
-
export class SemanticSliceExtractorRegistry {
|
|
10
|
-
private static extractors: SemanticSliceExtractor[] = [
|
|
11
|
-
new LaravelSemanticExtractor(),
|
|
12
|
-
new McpCliSemanticExtractor(),
|
|
13
|
-
new NestJsSemanticExtractor(),
|
|
14
|
-
];
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Registers a new semantic slice extractor strategy.
|
|
18
|
-
* Prepends to the list to give higher priority to custom extractors.
|
|
19
|
-
*/
|
|
20
|
-
public static register(extractor: SemanticSliceExtractor): void {
|
|
21
|
-
this.extractors.unshift(extractor);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Returns all extractors capable of handling the specified project topology.
|
|
26
|
-
*/
|
|
27
|
-
public static getExtractors(projectRoot: string, framework?: string): SemanticSliceExtractor[] {
|
|
28
|
-
return this.extractors.filter((extractor) => extractor.canHandle(projectRoot, framework));
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Executes all matched extractors and combines their vertical slices.
|
|
33
|
-
*/
|
|
34
|
-
public static async extractAllSlices(
|
|
35
|
-
projectRoot: string,
|
|
36
|
-
framework?: string,
|
|
37
|
-
domainId?: number
|
|
38
|
-
): Promise<VerticalSliceCandidate[]> {
|
|
39
|
-
const matched = this.getExtractors(projectRoot, framework);
|
|
40
|
-
const combinedSlices: VerticalSliceCandidate[] = [];
|
|
41
|
-
|
|
42
|
-
for (const extractor of matched) {
|
|
43
|
-
const slices = await extractor.extractSlices(projectRoot, domainId);
|
|
44
|
-
combinedSlices.push(...slices);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
return combinedSlices;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
1
|
+
import type {
|
|
2
|
+
SemanticSliceExtractor,
|
|
3
|
+
VerticalSliceCandidate,
|
|
4
|
+
} from "./semantic-extractor.interface.ts";
|
|
5
|
+
import { LaravelSemanticExtractor } from "./laravel-semantic.ts";
|
|
6
|
+
import { McpCliSemanticExtractor } from "./mcp-cli-semantic.ts";
|
|
7
|
+
import { NestJsSemanticExtractor } from "./nestjs-semantic.ts";
|
|
8
|
+
|
|
9
|
+
export class SemanticSliceExtractorRegistry {
|
|
10
|
+
private static extractors: SemanticSliceExtractor[] = [
|
|
11
|
+
new LaravelSemanticExtractor(),
|
|
12
|
+
new McpCliSemanticExtractor(),
|
|
13
|
+
new NestJsSemanticExtractor(),
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Registers a new semantic slice extractor strategy.
|
|
18
|
+
* Prepends to the list to give higher priority to custom extractors.
|
|
19
|
+
*/
|
|
20
|
+
public static register(extractor: SemanticSliceExtractor): void {
|
|
21
|
+
this.extractors.unshift(extractor);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Returns all extractors capable of handling the specified project topology.
|
|
26
|
+
*/
|
|
27
|
+
public static getExtractors(projectRoot: string, framework?: string): SemanticSliceExtractor[] {
|
|
28
|
+
return this.extractors.filter((extractor) => extractor.canHandle(projectRoot, framework));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Executes all matched extractors and combines their vertical slices.
|
|
33
|
+
*/
|
|
34
|
+
public static async extractAllSlices(
|
|
35
|
+
projectRoot: string,
|
|
36
|
+
framework?: string,
|
|
37
|
+
domainId?: number
|
|
38
|
+
): Promise<VerticalSliceCandidate[]> {
|
|
39
|
+
const matched = this.getExtractors(projectRoot, framework);
|
|
40
|
+
const combinedSlices: VerticalSliceCandidate[] = [];
|
|
41
|
+
|
|
42
|
+
for (const extractor of matched) {
|
|
43
|
+
const slices = await extractor.extractSlices(projectRoot, domainId);
|
|
44
|
+
combinedSlices.push(...slices);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return combinedSlices;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
import type { VerticalSliceRecord } from "../../../types/index.ts";
|
|
2
|
-
|
|
3
|
-
export type VerticalSliceCandidate = Omit<VerticalSliceRecord, "id" | "created_at">;
|
|
4
|
-
|
|
5
|
-
export interface SemanticSliceExtractor {
|
|
6
|
-
readonly id: string;
|
|
7
|
-
readonly name: string;
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Determines if this extractor can handle the project topology.
|
|
11
|
-
*/
|
|
12
|
-
canHandle(projectRoot: string, framework?: string): boolean;
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Extracts vertical slices from the codebase.
|
|
16
|
-
*/
|
|
17
|
-
extractSlices(
|
|
18
|
-
projectRoot: string,
|
|
19
|
-
domainId?: number
|
|
20
|
-
): Promise<VerticalSliceCandidate[]> | VerticalSliceCandidate[];
|
|
21
|
-
}
|
|
1
|
+
import type { VerticalSliceRecord } from "../../../types/index.ts";
|
|
2
|
+
|
|
3
|
+
export type VerticalSliceCandidate = Omit<VerticalSliceRecord, "id" | "created_at">;
|
|
4
|
+
|
|
5
|
+
export interface SemanticSliceExtractor {
|
|
6
|
+
readonly id: string;
|
|
7
|
+
readonly name: string;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Determines if this extractor can handle the project topology.
|
|
11
|
+
*/
|
|
12
|
+
canHandle(projectRoot: string, framework?: string): boolean;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Extracts vertical slices from the codebase.
|
|
16
|
+
*/
|
|
17
|
+
extractSlices(
|
|
18
|
+
projectRoot: string,
|
|
19
|
+
domainId?: number
|
|
20
|
+
): Promise<VerticalSliceCandidate[]> | VerticalSliceCandidate[];
|
|
21
|
+
}
|