septum 0.1.3 → 0.1.5

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 (74) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +105 -102
  3. package/bin/septum.ts +4 -4
  4. package/package.json +63 -63
  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 +48 -90
  13. package/src/cli/commands/sync.ts +31 -31
  14. package/src/cli/commands/topology.ts +119 -0
  15. package/src/cli/index.ts +210 -199
  16. package/src/cli/templates/hooks.ts +75 -75
  17. package/src/cli/templates/rules.ts +38 -38
  18. package/src/cli/templates/skill.ts +69 -69
  19. package/src/core/boundary/evaluator.ts +232 -210
  20. package/src/core/config/loader.ts +97 -97
  21. package/src/core/config/schema.ts +39 -39
  22. package/src/core/database/client.ts +44 -44
  23. package/src/core/database/repositories/dependency.repository.ts +366 -349
  24. package/src/core/database/repositories/domain.repository.ts +218 -198
  25. package/src/core/database/repositories/environment.repository.ts +207 -0
  26. package/src/core/database/repositories/file.repository.ts +116 -106
  27. package/src/core/database/repositories/meta.repository.ts +33 -33
  28. package/src/core/database/repositories/symbol.repository.ts +443 -424
  29. package/src/core/database/repository.ts +264 -255
  30. package/src/core/database/schema.ts +34 -1
  31. package/src/core/discovery/topology-detector.ts +486 -486
  32. package/src/core/ingestion/hasher.ts +5 -5
  33. package/src/core/ingestion/pipeline.ts +584 -535
  34. package/src/core/parser/behavioral-analyzer.ts +119 -0
  35. package/src/core/parser/boundary-tracker.ts +288 -128
  36. package/src/core/parser/extractors/base.ts +62 -61
  37. package/src/core/parser/extractors/frontend.ts +194 -194
  38. package/src/core/parser/extractors/go.ts +229 -176
  39. package/src/core/parser/extractors/laravel-semantic.ts +1039 -585
  40. package/src/core/parser/extractors/mcp-cli-semantic.ts +199 -199
  41. package/src/core/parser/extractors/nestjs-semantic.ts +119 -119
  42. package/src/core/parser/extractors/php.ts +153 -149
  43. package/src/core/parser/extractors/python.ts +228 -226
  44. package/src/core/parser/extractors/registry.ts +70 -70
  45. package/src/core/parser/extractors/semantic-extractor.interface.ts +21 -21
  46. package/src/core/parser/extractors/typescript.ts +181 -177
  47. package/src/core/parser/schema/providers/laravel-migration-provider.ts +215 -0
  48. package/src/core/parser/schema/providers/prisma-schema-provider.ts +89 -0
  49. package/src/core/parser/schema/schema-provider-registry.ts +43 -0
  50. package/src/core/parser/schema/schema-provider.interface.ts +33 -0
  51. package/src/core/parser/tree-sitter.ts +41 -41
  52. package/src/core/resolver/call-graph-tracer.ts +666 -668
  53. package/src/core/resolver/module-resolver.ts +408 -342
  54. package/src/core/resolver/symbol-locator.ts +543 -543
  55. package/src/core/resolver/vertical-slice-tracer.ts +301 -252
  56. package/src/core/session/session-manager.ts +56 -56
  57. package/src/core/telemetry/telemetry.ts +86 -86
  58. package/src/core/topology/environment-detector.ts +860 -0
  59. package/src/core/topology/types.ts +83 -0
  60. package/src/index.ts +21 -18
  61. package/src/mcp/schemas.ts +578 -238
  62. package/src/mcp/server.ts +875 -656
  63. package/src/mcp/tools/check-boundary.ts +171 -152
  64. package/src/mcp/tools/get-domain-catalog.ts +145 -121
  65. package/src/mcp/tools/get-environment-topology.ts +141 -0
  66. package/src/mcp/tools/get-feature-context.ts +212 -116
  67. package/src/mcp/tools/get-symbol-hotspots.ts +60 -49
  68. package/src/mcp/tools/get-symbol-impact.ts +28 -26
  69. package/src/mcp/tools/get-symbol.ts +65 -63
  70. package/src/mcp/tools/locate-symbol.ts +171 -190
  71. package/src/mcp/tools/register-domain.ts +68 -64
  72. package/src/mcp/tools/register-feature.ts +100 -0
  73. package/src/mcp/tools/trace-vertical-slice.ts +74 -103
  74. package/src/types/index.ts +335 -299
@@ -1,226 +1,228 @@
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, calculatePythonNesting } 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
+ const nestingDepth = calculatePythonNesting(lines, i, endLine - 1);
210
+ symbols.push({
211
+ name: isMethod ? `${currentClass}::${funcName}` : funcName,
212
+ kind: isMethod ? "method" : "function",
213
+ signature: trimmed.replace(/:$/, "").trim(),
214
+ visibility,
215
+ line_start: lineNum,
216
+ line_end: endLine,
217
+ line_count: Math.max(1, endLine - lineNum + 1),
218
+ nesting_depth: nestingDepth,
219
+ });
220
+ }
221
+ }
222
+
223
+ return {
224
+ symbols,
225
+ dependencies,
226
+ };
227
+ }
228
+ }
@@ -1,70 +1,70 @@
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
-
50
- /**
51
- * Synchronous execution of all matched semantic extractors.
52
- */
53
- public static extractAllSlicesSync(
54
- projectRoot: string,
55
- framework?: string,
56
- domainId?: number
57
- ): VerticalSliceCandidate[] {
58
- const matched = this.getExtractors(projectRoot, framework);
59
- const combinedSlices: VerticalSliceCandidate[] = [];
60
-
61
- for (const extractor of matched) {
62
- const res = extractor.extractSlices(projectRoot, domainId);
63
- if (Array.isArray(res)) {
64
- combinedSlices.push(...res);
65
- }
66
- }
67
-
68
- return combinedSlices;
69
- }
70
- }
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
+
50
+ /**
51
+ * Synchronous execution of all matched semantic extractors.
52
+ */
53
+ public static extractAllSlicesSync(
54
+ projectRoot: string,
55
+ framework?: string,
56
+ domainId?: number
57
+ ): VerticalSliceCandidate[] {
58
+ const matched = this.getExtractors(projectRoot, framework);
59
+ const combinedSlices: VerticalSliceCandidate[] = [];
60
+
61
+ for (const extractor of matched) {
62
+ const res = extractor.extractSlices(projectRoot, domainId);
63
+ if (Array.isArray(res)) {
64
+ combinedSlices.push(...res);
65
+ }
66
+ }
67
+
68
+ return combinedSlices;
69
+ }
70
+ }
@@ -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
+ }