septum 0.1.2 → 0.1.4

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 (46) hide show
  1. package/README.md +7 -2
  2. package/package.json +1 -1
  3. package/src/cli/commands/slice.ts +4 -46
  4. package/src/cli/commands/topology.ts +119 -0
  5. package/src/cli/index.ts +11 -0
  6. package/src/cli/templates/skill.ts +7 -4
  7. package/src/core/config/loader.ts +21 -8
  8. package/src/core/database/repositories/dependency.repository.ts +70 -0
  9. package/src/core/database/repositories/environment.repository.ts +207 -0
  10. package/src/core/database/repositories/file.repository.ts +7 -0
  11. package/src/core/database/repositories/symbol.repository.ts +28 -9
  12. package/src/core/database/repository.ts +19 -1
  13. package/src/core/database/schema.ts +153 -120
  14. package/src/core/discovery/topology-detector.ts +71 -3
  15. package/src/core/ingestion/pipeline.ts +98 -1
  16. package/src/core/parser/behavioral-analyzer.ts +119 -0
  17. package/src/core/parser/boundary-tracker.ts +160 -0
  18. package/src/core/parser/extractors/base.ts +2 -0
  19. package/src/core/parser/extractors/blade.ts +158 -0
  20. package/src/core/parser/extractors/go.ts +5 -1
  21. package/src/core/parser/extractors/laravel-semantic.ts +506 -31
  22. package/src/core/parser/extractors/php.ts +5 -1
  23. package/src/core/parser/extractors/python.ts +3 -1
  24. package/src/core/parser/extractors/registry.ts +21 -0
  25. package/src/core/parser/extractors/typescript.ts +5 -1
  26. package/src/core/parser/schema/providers/laravel-migration-provider.ts +215 -0
  27. package/src/core/parser/schema/providers/prisma-schema-provider.ts +89 -0
  28. package/src/core/parser/schema/schema-provider-registry.ts +43 -0
  29. package/src/core/parser/schema/schema-provider.interface.ts +33 -0
  30. package/src/core/parser/tree-sitter.ts +2 -0
  31. package/src/core/resolver/path-resolver.ts +147 -0
  32. package/src/core/resolver/vertical-slice-tracer.ts +56 -8
  33. package/src/core/topology/environment-detector.ts +860 -0
  34. package/src/core/topology/types.ts +83 -0
  35. package/src/index.ts +3 -0
  36. package/src/mcp/schemas.ts +264 -46
  37. package/src/mcp/server.ts +130 -26
  38. package/src/mcp/tools/check-boundary.ts +58 -8
  39. package/src/mcp/tools/get-domain-catalog.ts +31 -1
  40. package/src/mcp/tools/get-environment-topology.ts +141 -0
  41. package/src/mcp/tools/get-symbol-hotspots.ts +13 -3
  42. package/src/mcp/tools/locate-symbol.ts +119 -3
  43. package/src/mcp/tools/register-domain.ts +1 -1
  44. package/src/mcp/tools/trace-vertical-slice.ts +11 -65
  45. package/src/types/index.ts +20 -1
  46. package/src/version.ts +3 -3
@@ -0,0 +1,83 @@
1
+ export type EnvironmentLayer =
2
+ | "edge"
3
+ | "gateway"
4
+ | "host"
5
+ | "runtime"
6
+ | "storage"
7
+ | "telemetry";
8
+
9
+ export type LayerResolutionStatus = "DETECTED" | "AMBIGUOUS" | "UNRESOLVED" | "VERIFIED";
10
+
11
+ export type CanonicalPlatformTag =
12
+ | "shared_hosting"
13
+ | "vps"
14
+ | "docker"
15
+ | "serverless"
16
+ | "kubernetes"
17
+ | "managed_db"
18
+ | "cdn_edge"
19
+ | "paas"
20
+ | "static_cdn"
21
+ | "unspecified";
22
+
23
+ export interface EnvironmentLayerInfo {
24
+ layer: EnvironmentLayer;
25
+ platform?: string;
26
+ canonical_tag?: CanonicalPlatformTag;
27
+ status: LayerResolutionStatus;
28
+ detected_from?: string[];
29
+ evidence?: string[];
30
+ constraints?: string[];
31
+ unresolved_inquiry?: string;
32
+ }
33
+
34
+ export interface TopologyNode {
35
+ id: string; // e.g. "edge:cloudflare", "host:api-vps", "host:worker-vps", "storage:central-db"
36
+ layer: EnvironmentLayer;
37
+ name: string; // e.g. "Cloudflare Perimeter", "API Backend VPS", "Postgres Cluster"
38
+ platform: string; // e.g. "Cloudflare WAF", "Hostinger VPS", "AWS RDS Postgres"
39
+ canonical_tag: CanonicalPlatformTag;
40
+ domain_or_ip?: string; // e.g. "api.example.com", "10.0.0.5"
41
+ role?: string; // e.g. "web_api", "queue_worker", "frontend_spa", "database_primary"
42
+ status: LayerResolutionStatus;
43
+ detected_from: string[];
44
+ constraints: string[];
45
+ connected_to?: string[]; // IDs of nodes this node interacts with
46
+ unresolved_inquiry?: string;
47
+ }
48
+
49
+ export interface CrossLayerInterplay {
50
+ title: string;
51
+ involved_layers: EnvironmentLayer[];
52
+ involved_node_ids?: string[];
53
+ risk_or_rule: string;
54
+ required_action: string;
55
+ }
56
+
57
+ export interface EnvironmentTopology {
58
+ is_distributed: boolean;
59
+ nodes: TopologyNode[];
60
+ layers: Record<EnvironmentLayer, EnvironmentLayerInfo>;
61
+ overall_status: "RESOLVED" | "NEEDS_CLARIFICATION";
62
+ interplays: CrossLayerInterplay[];
63
+ pending_inquiries: string[];
64
+ architectural_summary: string;
65
+ }
66
+
67
+ export interface ResolveLayerInput {
68
+ layer: EnvironmentLayer;
69
+ platform: string;
70
+ constraints?: string[];
71
+ }
72
+
73
+ export interface ResolveNodeInput {
74
+ id: string;
75
+ layer: EnvironmentLayer;
76
+ name: string;
77
+ platform: string;
78
+ canonical_tag?: CanonicalPlatformTag;
79
+ domain_or_ip?: string;
80
+ role?: string;
81
+ constraints?: string[];
82
+ connected_to?: string[];
83
+ }
package/src/index.ts CHANGED
@@ -9,6 +9,9 @@ export * from "./core/ingestion/pipeline.ts";
9
9
  export * from "./core/boundary/evaluator.ts";
10
10
  export * from "./core/resolver/call-graph-tracer.ts";
11
11
  export * from "./core/resolver/vertical-slice-tracer.ts";
12
+ export * from "./core/topology/types.ts";
13
+ export * from "./core/topology/environment-detector.ts";
14
+ export * from "./mcp/tools/get-environment-topology.ts";
12
15
  export * from "./mcp/server.ts";
13
16
  export * from "./cli/index.ts";
14
17
 
@@ -1,68 +1,286 @@
1
1
  import { z } from "zod";
2
2
 
3
- export const GetDomainCatalogSchema = z.object({
4
- domain: z.string().optional(),
5
- archetype: z.string().optional(),
6
- });
3
+ function withAliases(raw: unknown, aliases: Record<string, string>): unknown {
4
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return raw;
5
+ const obj = { ...(raw as Record<string, unknown>) };
6
+ for (const [alias, target] of Object.entries(aliases)) {
7
+ if (obj[alias] !== undefined && obj[target] === undefined) {
8
+ obj[target] = obj[alias];
9
+ }
10
+ }
11
+ return obj;
12
+ }
13
+
14
+ function normalizeBoundaryArgs(raw: unknown): unknown {
15
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return raw;
16
+ const obj = { ...(raw as Record<string, unknown>) };
17
+ const aliases: Record<string, string> = {
18
+ filePath: "file_path",
19
+ file: "file_path",
20
+ path: "file_path",
21
+ filePaths: "file_paths",
22
+ files: "file_paths",
23
+ paths: "file_paths",
24
+ proposedImports: "proposed_imports",
25
+ imports: "proposed_imports",
26
+ featureKey: "feature_key",
27
+ feature: "feature_key",
28
+ };
29
+ for (const [alias, target] of Object.entries(aliases)) {
30
+ if (obj[alias] !== undefined && obj[target] === undefined) {
31
+ obj[target] = obj[alias];
32
+ }
33
+ }
34
+ if (Array.isArray(obj.file_path) && !obj.file_paths) {
35
+ obj.file_paths = obj.file_path;
36
+ delete obj.file_path;
37
+ }
38
+ if (typeof obj.file_paths === "string" && !obj.file_path) {
39
+ obj.file_path = obj.file_paths;
40
+ delete obj.file_paths;
41
+ }
42
+ return obj;
43
+ }
44
+
45
+ export const GetDomainCatalogSchema = z.preprocess(
46
+ (args) =>
47
+ withAliases(args, {
48
+ domain_name: "domain",
49
+ domainName: "domain",
50
+ name: "domain",
51
+ archetype_filter: "archetype",
52
+ archetypeFilter: "archetype",
53
+ type: "archetype",
54
+ workspace: "workspace_path",
55
+ workspacePath: "workspace_path",
56
+ project_root: "workspace_path",
57
+ target_path: "workspace_path",
58
+ path: "workspace_path",
59
+ }),
60
+ z.object({
61
+ domain: z.string().optional(),
62
+ archetype: z.string().optional(),
63
+ workspace_path: z.string().optional(),
64
+ })
65
+ );
7
66
  export type GetDomainCatalogArgs = z.infer<typeof GetDomainCatalogSchema>;
8
67
 
9
- export const GetFeatureContextSchema = z.object({
10
- feature: z.string().min(1, "feature is required"),
11
- });
68
+ export const GetFeatureContextSchema = z.preprocess(
69
+ (args) =>
70
+ withAliases(args, {
71
+ feature_key: "feature",
72
+ featureKey: "feature",
73
+ name: "feature",
74
+ workspace: "workspace_path",
75
+ workspacePath: "workspace_path",
76
+ }),
77
+ z.object({
78
+ feature: z.string().min(1, "feature is required"),
79
+ workspace_path: z.string().optional(),
80
+ })
81
+ );
12
82
  export type GetFeatureContextArgs = z.infer<typeof GetFeatureContextSchema>;
13
83
 
14
- export const LocateSymbolSchema = z.object({
15
- query: z.string().min(1, "query is required"),
16
- domain: z.string().optional(),
17
- limit: z.number().int().positive().optional().default(10),
18
- });
84
+ export const LocateSymbolSchema = z.preprocess(
85
+ (args) =>
86
+ withAliases(args, {
87
+ symbol: "query",
88
+ name: "query",
89
+ symbol_name: "query",
90
+ symbolName: "query",
91
+ target: "query",
92
+ domain_name: "domain",
93
+ domainName: "domain",
94
+ max: "limit",
95
+ workspace: "workspace_path",
96
+ workspacePath: "workspace_path",
97
+ project_root: "workspace_path",
98
+ target_path: "workspace_path",
99
+ path: "workspace_path",
100
+ }),
101
+ z.object({
102
+ query: z.string().min(1, "query is required"),
103
+ domain: z.string().optional(),
104
+ limit: z.number().int().positive().optional().default(10),
105
+ workspace_path: z.string().optional(),
106
+ })
107
+ );
19
108
  export type LocateSymbolArgs = z.infer<typeof LocateSymbolSchema>;
20
109
 
21
- export const GetSymbolSchema = z.object({
22
- symbol: z.string().min(1, "symbol is required"),
23
- domain: z.string().optional(),
24
- include_dependencies: z.boolean().optional().default(true),
25
- });
110
+ export const GetSymbolSchema = z.preprocess(
111
+ (args) =>
112
+ withAliases(args, {
113
+ query: "symbol",
114
+ name: "symbol",
115
+ symbol_name: "symbol",
116
+ symbolName: "symbol",
117
+ target: "symbol",
118
+ domain_name: "domain",
119
+ domainName: "domain",
120
+ includeDependencies: "include_dependencies",
121
+ include_deps: "include_dependencies",
122
+ includeDeps: "include_dependencies",
123
+ dependencies: "include_dependencies",
124
+ deps: "include_dependencies",
125
+ }),
126
+ z.object({
127
+ symbol: z.string().min(1, "symbol is required"),
128
+ domain: z.string().optional(),
129
+ include_dependencies: z.boolean().optional().default(true),
130
+ })
131
+ );
26
132
  export type GetSymbolArgs = z.infer<typeof GetSymbolSchema>;
27
133
 
28
- export const GetSymbolImpactSchema = z.object({
29
- symbol: z.string().min(1, "symbol is required"),
30
- });
134
+ export const GetSymbolImpactSchema = z.preprocess(
135
+ (args) =>
136
+ withAliases(args, {
137
+ query: "symbol",
138
+ name: "symbol",
139
+ symbol_name: "symbol",
140
+ symbolName: "symbol",
141
+ file_path: "symbol",
142
+ filePath: "symbol",
143
+ file: "symbol",
144
+ path: "symbol",
145
+ target: "symbol",
146
+ }),
147
+ z.object({
148
+ symbol: z.string().min(1, "symbol is required"),
149
+ })
150
+ );
31
151
  export type GetSymbolImpactArgs = z.infer<typeof GetSymbolImpactSchema>;
32
152
 
33
- export const TraceVerticalSliceSchema = z.object({
34
- query: z.string().min(1, "query is required"),
35
- max_depth: z.number().int().positive().optional().default(5),
36
- });
153
+ export const TraceVerticalSliceSchema = z.preprocess(
154
+ (args) =>
155
+ withAliases(args, {
156
+ route: "query",
157
+ uri: "query",
158
+ route_uri: "query",
159
+ routeUri: "query",
160
+ symbol: "query",
161
+ name: "query",
162
+ feature: "query",
163
+ feature_key: "query",
164
+ featureKey: "query",
165
+ maxDepth: "max_depth",
166
+ depth: "max_depth",
167
+ }),
168
+ z.object({
169
+ query: z.string().min(1, "query is required"),
170
+ max_depth: z.number().int().positive().optional().default(5),
171
+ })
172
+ );
37
173
  export type TraceVerticalSliceArgs = z.infer<typeof TraceVerticalSliceSchema>;
38
174
 
39
- export const CheckBoundarySchema = z.object({
40
- file_path: z.string().optional(),
41
- file_paths: z.array(z.string()).optional(),
42
- proposed_imports: z.array(z.string()).default([]),
43
- feature_key: z.string().optional(),
44
- });
175
+ export const CheckBoundarySchema = z.preprocess(
176
+ normalizeBoundaryArgs,
177
+ z.object({
178
+ file_path: z.string().optional(),
179
+ file_paths: z.array(z.string()).optional(),
180
+ proposed_imports: z.array(z.string()).default([]),
181
+ feature_key: z.string().optional(),
182
+ })
183
+ );
45
184
  export type CheckBoundaryArgs = z.infer<typeof CheckBoundarySchema>;
46
185
 
47
- export const GetSymbolHotspotsSchema = z.object({
48
- domain: z.string().optional(),
49
- limit: z.number().int().positive().optional().default(10),
50
- });
186
+ export const GetSymbolHotspotsSchema = z.preprocess(
187
+ (args) =>
188
+ withAliases(args, {
189
+ domain_name: "domain",
190
+ domainName: "domain",
191
+ max: "limit",
192
+ nesting: "min_nesting",
193
+ minNesting: "min_nesting",
194
+ lines: "min_lines",
195
+ minLines: "min_lines",
196
+ sortBy: "sort_by",
197
+ }),
198
+ z.object({
199
+ min_lines: z.number().int().nonnegative().optional(),
200
+ min_nesting: z.number().int().positive().optional(),
201
+ kind: z.string().optional(),
202
+ domain: z.string().optional(),
203
+ limit: z.number().int().positive().optional().default(10),
204
+ sort_by: z.enum(["lines", "nesting", "risk_score"]).optional(),
205
+ })
206
+ );
51
207
  export type GetSymbolHotspotsArgs = z.infer<typeof GetSymbolHotspotsSchema>;
52
208
 
53
- export const RegisterDomainSchema = z.object({
54
- name: z.string().min(1, "Domain name is required"),
55
- root: z.string().min(1, "Domain root path is required"),
56
- description: z.string().optional(),
57
- allowed_dependencies: z.array(z.string()).optional(),
58
- forbidden_dependencies: z.array(z.string()).optional(),
59
- archetypes: z.record(z.string()).optional(),
60
- ingest_now: z.boolean().optional().default(false),
61
- });
209
+ export const RegisterDomainSchema = z.preprocess(
210
+ (args) =>
211
+ withAliases(args, {
212
+ domain: "name",
213
+ domain_name: "name",
214
+ domainName: "name",
215
+ root_path: "root",
216
+ rootPath: "root",
217
+ path: "root",
218
+ allowedDependencies: "allowed_dependencies",
219
+ allowed: "allowed_dependencies",
220
+ forbiddenDependencies: "forbidden_dependencies",
221
+ forbidden: "forbidden_dependencies",
222
+ archetype: "archetypes",
223
+ ingestNow: "ingest_now",
224
+ }),
225
+ z.object({
226
+ name: z.string().min(1, "Domain name is required"),
227
+ root: z.string().min(1, "Domain root path is required"),
228
+ description: z.string().optional(),
229
+ allowed_dependencies: z.array(z.string()).optional(),
230
+ forbidden_dependencies: z.array(z.string()).optional(),
231
+ archetypes: z.record(z.string()).optional(),
232
+ ingest_now: z.boolean().optional().default(false),
233
+ })
234
+ );
62
235
  export type RegisterDomainArgs = z.infer<typeof RegisterDomainSchema>;
63
236
 
64
- export const AutoDiscoverDomainsSchema = z.object({
65
- persist: z.boolean().optional().default(true),
66
- });
237
+ export const AutoDiscoverDomainsSchema = z.preprocess(
238
+ (args) =>
239
+ withAliases(args, {
240
+ save: "persist",
241
+ }),
242
+ z.object({
243
+ persist: z.boolean().optional().default(true),
244
+ })
245
+ );
67
246
  export type AutoDiscoverDomainsArgs = z.infer<typeof AutoDiscoverDomainsSchema>;
68
247
 
248
+ export const GetEnvironmentTopologySchema = z.object({
249
+ resolve: z
250
+ .object({
251
+ layer: z.enum(["edge", "gateway", "host", "runtime", "storage", "telemetry"]),
252
+ platform: z.string().min(1, "platform name cannot be empty"),
253
+ constraints: z.array(z.string()).optional(),
254
+ })
255
+ .optional(),
256
+ resolveNode: z
257
+ .object({
258
+ id: z.string().min(1, "Node id cannot be empty"),
259
+ layer: z.enum(["edge", "gateway", "host", "runtime", "storage", "telemetry"]),
260
+ name: z.string().min(1, "Node name cannot be empty"),
261
+ platform: z.string().min(1, "Platform cannot be empty"),
262
+ canonical_tag: z
263
+ .enum([
264
+ "shared_hosting",
265
+ "vps",
266
+ "docker",
267
+ "serverless",
268
+ "kubernetes",
269
+ "managed_db",
270
+ "cdn_edge",
271
+ "paas",
272
+ "static_cdn",
273
+ "unspecified",
274
+ ])
275
+ .optional(),
276
+ domain_or_ip: z.string().optional(),
277
+ role: z.string().optional(),
278
+ constraints: z.array(z.string()).optional(),
279
+ connected_to: z.array(z.string()).optional(),
280
+ })
281
+ .optional(),
282
+ refresh: z.boolean().optional().default(false),
283
+ });
284
+ export type GetEnvironmentTopologyArgs = z.infer<typeof GetEnvironmentTopologySchema>;
285
+
286
+