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,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
+ }